diff --git a/WooCommerce/Classes/Analytics/WooAnalytics.swift b/WooCommerce/Classes/Analytics/WooAnalytics.swift index a2121996b9b..0194617f327 100644 --- a/WooCommerce/Classes/Analytics/WooAnalytics.swift +++ b/WooCommerce/Classes/Analytics/WooAnalytics.swift @@ -4,6 +4,7 @@ import UIKit import WordPressShared import WidgetKit import enum Alamofire.AFError +import enum Networking.NetworkError import Yosemite import protocol WooFoundation.Analytics import protocol WooFoundation.AnalyticsProvider @@ -218,18 +219,20 @@ private extension Analytics { return nil } - let err = error as NSError + let nsError = error as NSError let errorCode: String = { - if let networkError = error as? AFError { - if let responseCode = networkError.responseCode { - return "\(responseCode)" - } else if let underlyingError = networkError.underlyingError as? NSError { - return "\(underlyingError.code)" + if let networkError = error as? NetworkError, let code = networkError.responseCode { + return code.description + } else if let afError = error as? AFError { + if let responseCode = afError.responseCode { + return responseCode.description + } else if let underlyingError = afError.underlyingError as? NSError { + return underlyingError.code.description } } else if let loginError = error as? SiteCredentialLoginError { - return "\(loginError.underlyingError.code)" + return loginError.underlyingError.code.description } - return "\(err.code)" + return nsError.code.description }() let errorDomain: String = { @@ -237,10 +240,10 @@ private extension Analytics { let underlyingError = networkError.underlyingError as? NSError { return underlyingError.domain } - return err.domain + return nsError.domain }() - let errorDescription = err.description + let errorDescription = nsError.description return [ Constants.errorKeyCode: errorCode, diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsEvent+JetpackSetup.swift b/WooCommerce/Classes/Analytics/WooAnalyticsEvent+JetpackSetup.swift index 8c0e4db422a..132f14de6e2 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsEvent+JetpackSetup.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsEvent+JetpackSetup.swift @@ -9,6 +9,8 @@ extension WooAnalyticsEvent { case tap case step case isSignup = "is_signup" + case connectionType = "connection_type" + case usingApplicationPassword = "is_using_application_password" } enum LoginFlow { @@ -35,6 +37,11 @@ extension WooAnalyticsEvent { } } + enum ConnectionType: String { + case native + case web + } + static func connectionCheckCompleted(isAlreadyConnected: Bool, requiresConnectionOnly: Bool) -> WooAnalyticsEvent { .init(statName: .jetpackSetupConnectionCheckCompleted, properties: [ Key.isAlreadyConnected.rawValue: isAlreadyConnected, @@ -56,8 +63,24 @@ extension WooAnalyticsEvent { return .init(statName: .jetpackSetupLoginFlow, properties: properties, error: failure) } - static func setupFlow(step: JetpackInstallStep, tap: SetupFlow.TapTarget? = nil, failure: Error? = nil) -> WooAnalyticsEvent { - var properties: [String: WooAnalyticsEventPropertyType] = [Key.step.rawValue: step.analyticsValue] + static func setupFlow(step: JetpackInstallStep, + tap: SetupFlow.TapTarget? = nil, + connectionType: ConnectionType, + failure: Error? = nil) -> WooAnalyticsEvent { + let isApplicationPassword: Bool = { + let credentials = ServiceLocator.stores.sessionManager.defaultCredentials + switch credentials { + case .some(.applicationPassword): + return true + default: + return false + } + }() + var properties: [String: WooAnalyticsEventPropertyType] = [ + Key.step.rawValue: step.analyticsValue, + Key.connectionType.rawValue: connectionType.rawValue, + Key.usingApplicationPassword.rawValue: isApplicationPassword + ] if let tap { properties[Key.tap.rawValue] = tap.rawValue } diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index 392dc89f3aa..b3d0c9336a5 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -107,32 +107,6 @@ enum WooAnalyticsStat: String { case loginSiteCredentialsAppPasswordLoginExitConfirmation = "login_site_credentials_app_password_login_exit_confirmation" case loginSiteCredentialsAppPasswordLoginDismissed = "login_site_credentials_app_password_login_dismissed" - - // MARK: Install/Setup Jetpack (`LoginJetpackSetupView`) - // - case loginJetpackSetupScreenViewed = "login_jetpack_setup_screen_viewed" - case loginJetpackSetupScreenDismissed = "login_jetpack_setup_screen_dismissed" - - case loginJetpackSetupScreenInstallSuccessful = "login_jetpack_setup_install_successful" - case loginJetpackSetupScreenInstallFailed = "login_jetpack_setup_install_failed" - - case loginJetpackSetupActivationSuccessful = "login_jetpack_setup_activation_successful" - case loginJetpackSetupActivationFailed = "login_jetpack_setup_activation_failed" - - case loginJetpackSetupFetchJetpackConnectionURLSuccessful = "login_jetpack_setup_fetch_jetpack_connection_url_successful" - case loginJetpackSetupFetchJetpackConnectionURLFailed = "login_jetpack_setup_fetch_jetpack_connection_url_failed" - - case loginJetpackSetupCannotFindWPCOMUser = "login_jetpack_setup_cannot_find_WPCOM_user" - case loginJetpackSetupAllStepsMarkedDone = "login_jetpack_setup_all_steps_marked_done" - case loginJetpackSetupErrorCheckingJetpackConnection = "login_jetpack_setup_error_checking_jetpack_connection" - - case loginJetpackSetupGoToStoreTapped = "login_jetpack_setup_go_to_store_button_tapped" - - case loginJetpackSetupAuthorizedUsingDifferentWPCOMAccount = "login_jetpack_setup_authorized_using_different_wpcom_account" - - case loginJetpackSetupScreenTryAgainButtonTapped = "login_jetpack_setup_try_again_button_tapped" - case loginJetpackSetupScreenGetSupportTapped = "login_jetpack_setup_get_support_button_tapped" - // MARK: No matched site alert // case loginJetpackNoMatchedSiteErrorViewed = "login_jetpack_no_matched_site_error_viewed" diff --git a/WooCommerce/Classes/Authentication/Jetpack Setup/LoginJetpackSetupCoordinator.swift b/WooCommerce/Classes/Authentication/Jetpack Setup/LoginJetpackSetupCoordinator.swift index ef532e259d9..cf068171f36 100644 --- a/WooCommerce/Classes/Authentication/Jetpack Setup/LoginJetpackSetupCoordinator.swift +++ b/WooCommerce/Classes/Authentication/Jetpack Setup/LoginJetpackSetupCoordinator.swift @@ -52,7 +52,6 @@ private extension LoginJetpackSetupCoordinator { guard let self, let email = connectedEmail else { return } if email != self.stores.sessionManager.defaultAccount?.email { // if the user authorized Jetpack with a different account, support them to log in with that account. - self.analytics.track(.loginJetpackSetupAuthorizedUsingDifferentWPCOMAccount) self.showVerifyWPComAccount(email: email) } else { // dismiss the setup view diff --git a/WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupView.swift b/WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupView.swift index 2e8bdcdbe69..1bcfa78c48e 100644 --- a/WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupView.swift +++ b/WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupView.swift @@ -32,9 +32,7 @@ final class JetpackSetupHostingController: UIHostingController rootView.supportHandler = { [weak self] in guard let self else { return } - - self.viewModel.trackSetupDuringLogin(.loginJetpackSetupScreenGetSupportTapped, properties: self.viewModel.currentSetupStep?.analyticsDescription) - self.viewModel.trackSetupAfterLogin(tap: .support) + self.viewModel.trackSetup(tap: .support) self.presentSupport() } @@ -48,8 +46,6 @@ final class JetpackSetupHostingController: UIHostingController override func viewDidLoad() { super.viewDidLoad() - - viewModel.trackSetupDuringLogin(.loginJetpackSetupScreenViewed) configureNavigationBarAppearance() } @@ -62,8 +58,7 @@ final class JetpackSetupHostingController: UIHostingController @objc private func dismissView() { - viewModel.trackSetupDuringLogin(.loginJetpackSetupScreenDismissed, properties: viewModel.currentSetupStep?.analyticsDescription) - viewModel.trackSetupAfterLogin(tap: .dismiss) + viewModel.trackSetup(tap: .dismiss) dismiss(animated: true) } diff --git a/WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupViewModel.swift b/WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupViewModel.swift index bba77612719..cd56eb82d2b 100644 --- a/WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupViewModel.swift +++ b/WooCommerce/Classes/Authentication/Jetpack Setup/Native Jetpack Setup/JetpackSetupViewModel.swift @@ -17,6 +17,7 @@ final class JetpackSetupViewModel: ObservableObject { private let storeNavigationHandler: (_ connectedEmail: String?) -> Void private let wpcomCredentials: Credentials? private var isPluginActivated = false + private var connectionType = WooAnalyticsEvent.JetpackSetup.ConnectionType.native @Published private(set) var setupSteps: [JetpackInstallStep] @@ -151,15 +152,12 @@ final class JetpackSetupViewModel: ObservableObject { } func navigateToStore() { - trackSetupDuringLogin(.loginJetpackSetupGoToStoreTapped) - trackSetupAfterLogin(tap: .goToStore) + trackSetup(tap: .goToStore) storeNavigationHandler(jetpackConnectedEmail) } func retryAllSteps() { - trackSetupDuringLogin(.loginJetpackSetupScreenTryAgainButtonTapped, - properties: currentSetupStep?.analyticsDescription) - trackSetupAfterLogin(tap: .retry) + trackSetup(tap: .retry) setupFailed = false setupError = nil @@ -172,31 +170,18 @@ final class JetpackSetupViewModel: ObservableObject { /// LoginJetpackSetupInterruptedView func didTapContinueConnectionButton() { - trackSetupDuringLogin(.loginJetpackSetupScreenTryAgainButtonTapped) - trackSetupAfterLogin(tap: .continueSetup) + trackSetup(tap: .continueSetup) checkJetpackConnection(afterConnection: false) } - /// Tracks events if the current flow is Jetpack setup during login - func trackSetupDuringLogin(_ stat: WooAnalyticsStat, - properties: [AnyHashable: Any]? = nil, - failure: Error? = nil) { - guard stores.isAuthenticated == false else { - return - } - analytics.track(stat, properties: properties, error: failure) - } - /// Tracks events if the current flow is Jetpack setup after login with site credentials - func trackSetupAfterLogin(tap: WooAnalyticsEvent.JetpackSetup.SetupFlow.TapTarget? = nil, - failure: Error? = nil) { - guard stores.isAuthenticated else { - return - } + func trackSetup(tap: WooAnalyticsEvent.JetpackSetup.SetupFlow.TapTarget? = nil, + failure: Error? = nil) { /// Helper for analytics since `currentSetupStep` is optional. let currentStepForAnalytics: JetpackInstallStep = currentSetupStep ?? (connectionOnly ? .connection : .installation) analytics.track(event: .JetpackSetup.setupFlow(step: currentStepForAnalytics, tap: tap, + connectionType: connectionType, failure: failure)) } } @@ -236,17 +221,15 @@ private extension JetpackSetupViewModel { func installJetpack() { currentSetupStep = .installation - trackSetupAfterLogin() + trackSetup() let action = JetpackConnectionAction.installJetpackPlugin { [weak self] result in guard let self else { return } switch result { case .success: - self.trackSetupDuringLogin(.loginJetpackSetupScreenInstallSuccessful) self.activateJetpack() case .failure(let error): - self.trackSetupDuringLogin(.loginJetpackSetupScreenInstallFailed, failure: error) - self.trackSetupAfterLogin(failure: error) + self.trackSetup(failure: error) DDLogError("⛔️ Error installing Jetpack: \(error)") self.setupError = error self.setupFailed = true @@ -257,17 +240,15 @@ private extension JetpackSetupViewModel { func activateJetpack() { currentSetupStep = .activation - trackSetupAfterLogin() + trackSetup() let action = JetpackConnectionAction.activateJetpackPlugin { [weak self] result in guard let self else { return } switch result { case .success: isPluginActivated = true - self.trackSetupDuringLogin(.loginJetpackSetupActivationSuccessful) self.checkJetpackConnection(afterConnection: false) case .failure(let error): - self.trackSetupDuringLogin(.loginJetpackSetupActivationFailed, failure: error) - self.trackSetupAfterLogin(failure: error) + self.trackSetup(failure: error) DDLogError("⛔️ Error activating Jetpack: \(error)") self.setupError = error self.setupFailed = true @@ -281,12 +262,12 @@ private extension JetpackSetupViewModel { /// func startConnectionWithWebView() { currentSetupStep = .connection - trackSetupAfterLogin() + connectionType = .web + trackSetup() let action = JetpackConnectionAction.fetchJetpackConnectionURL { [weak self] result in guard let self else { return } switch result { case .success(let url): - self.trackSetupDuringLogin(.loginJetpackSetupFetchJetpackConnectionURLSuccessful) /// Checks if the fetch URL is for account connection; /// if not, use the web view solution to avoid the need for cookie-nonce. /// Reference: pe5sF9-1le-p2#comment-1942. @@ -297,8 +278,7 @@ private extension JetpackSetupViewModel { } self.shouldPresentWebView = true case .failure(let error): - self.trackSetupDuringLogin(.loginJetpackSetupFetchJetpackConnectionURLFailed, failure: error) - self.trackSetupAfterLogin(failure: error) + self.trackSetup(failure: error) DDLogError("⛔️ Error fetching Jetpack connection URL: \(error)") self.setupError = error self.setupFailed = true @@ -369,7 +349,6 @@ private extension JetpackSetupViewModel { let missingWpcomUserError = NSError(domain: Constants.errorDomain, code: Constants.errorCodeNoWPComUser, userInfo: [Constants.errorUserInfoReason: Constants.errorUserInfoNoWPComUser]) - trackSetupDuringLogin(.loginJetpackSetupCannotFindWPCOMUser, failure: missingWpcomUserError) if retryCount == Constants.maxRetryCount { return didFailJetpackConnection(with: missingWpcomUserError) } @@ -436,7 +415,7 @@ private extension JetpackSetupViewModel { func provisionSiteConnection(blogID: Int64) { currentSetupStep = .connection - trackSetupAfterLogin() + trackSetup() stores.dispatch(JetpackConnectionAction.provisionConnection(completion: { [weak self] result in guard let self else { return } switch result { @@ -475,17 +454,13 @@ private extension JetpackSetupViewModel { jetpackConnectedEmail = connectedEmail currentConnectionStep = .authorized currentSetupStep = .done - - trackSetupDuringLogin(.loginJetpackSetupAllStepsMarkedDone) - trackSetupAfterLogin() + trackSetup() } func didFailJetpackConnection(with error: Error) { - setupFailed = true setupError = error - if let setupError { - analytics.track(.loginJetpackSetupErrorCheckingJetpackConnection, withError: setupError) - } + trackSetup(failure: error) + setupFailed = true } } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/JCPJetpackInstall/JetpackInstallSteps.swift b/WooCommerce/Classes/ViewRelated/Dashboard/JCPJetpackInstall/JetpackInstallSteps.swift index 0ea025d0118..cf18309c752 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/JCPJetpackInstall/JetpackInstallSteps.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/JCPJetpackInstall/JetpackInstallSteps.swift @@ -97,12 +97,6 @@ extension JetpackInstallStep { } } - /// Description dictionary for Analytics - /// - var analyticsDescription: [String: String] { - ["jetpack_install_step": analyticsValue] - } - var analyticsValue: String { switch self { case .installation: diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index d065e0c6ac9..cb1e134cf72 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -2762,9 +2762,9 @@ DEA357132ADCC4C9006380BA /* BlazeCampaignListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA357122ADCC4C9006380BA /* BlazeCampaignListViewModelTests.swift */; }; DEA64C532E40B04700791018 /* OrderDetailsProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA64C522E40B04000791018 /* OrderDetailsProduct.swift */; }; DEA64C552E41A2D000791018 /* Product+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA64C542E41A2CA00791018 /* Product+Helpers.swift */; }; + DEA65B372E41A65600791018 /* ProductListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA65B362E41A65100791018 /* ProductListItem.swift */; }; DEA66A192E41DECF00791018 /* ShippingLabelProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA66A182E41DEC200791018 /* ShippingLabelProduct.swift */; }; DEA66A1B2E41E0C000791018 /* BlazeCampaignProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA66A1A2E41E0B800791018 /* BlazeCampaignProduct.swift */; }; - DEA65B372E41A65600791018 /* ProductListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA65B362E41A65100791018 /* ProductListItem.swift */; }; DEA6BCAF2BC6A9B10017D671 /* StoreStatsChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA6BCAE2BC6A9B10017D671 /* StoreStatsChart.swift */; }; DEA6BCB12BC6AA040017D671 /* StoreStatsChartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA6BCB02BC6AA040017D671 /* StoreStatsChartViewModel.swift */; }; DEA88F502AA9D0100037273B /* AddEditProductCategoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA88F4F2AA9D0100037273B /* AddEditProductCategoryViewModel.swift */; }; @@ -3017,7 +3017,6 @@ EEA3C2152CA3DB18000E82EC /* DefaultFavoriteProductsUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA3C2142CA3DB17000E82EC /* DefaultFavoriteProductsUseCaseTests.swift */; }; EEA3C21F2CA543D9000E82EC /* FavoriteProductsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA3C21D2CA543D9000E82EC /* FavoriteProductsUseCase.swift */; }; EEA3C2212CA5440B000E82EC /* MockFavoriteProductsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA3C2202CA5440A000E82EC /* MockFavoriteProductsUseCase.swift */; }; - EEAA45FD293073FE0047D125 /* JetpackInstallStepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAA45FC293073FE0047D125 /* JetpackInstallStepTests.swift */; }; EEADF622281A40CB001B40F1 /* ShippingValueLocalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEADF621281A40CB001B40F1 /* ShippingValueLocalizer.swift */; }; EEADF624281A421A001B40F1 /* DefaultShippingValueLocalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEADF623281A421A001B40F1 /* DefaultShippingValueLocalizer.swift */; }; EEADF626281A65A9001B40F1 /* DefaultShippingValueLocalizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEADF625281A65A9001B40F1 /* DefaultShippingValueLocalizerTests.swift */; }; @@ -5965,9 +5964,9 @@ DEA357122ADCC4C9006380BA /* BlazeCampaignListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignListViewModelTests.swift; sourceTree = ""; }; DEA64C522E40B04000791018 /* OrderDetailsProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsProduct.swift; sourceTree = ""; }; DEA64C542E41A2CA00791018 /* Product+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Product+Helpers.swift"; sourceTree = ""; }; + DEA65B362E41A65100791018 /* ProductListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductListItem.swift; sourceTree = ""; }; DEA66A182E41DEC200791018 /* ShippingLabelProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelProduct.swift; sourceTree = ""; }; DEA66A1A2E41E0B800791018 /* BlazeCampaignProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignProduct.swift; sourceTree = ""; }; - DEA65B362E41A65100791018 /* ProductListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductListItem.swift; sourceTree = ""; }; DEA6BCAE2BC6A9B10017D671 /* StoreStatsChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreStatsChart.swift; sourceTree = ""; }; DEA6BCB02BC6AA040017D671 /* StoreStatsChartViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreStatsChartViewModel.swift; sourceTree = ""; }; DEA88F4F2AA9D0100037273B /* AddEditProductCategoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditProductCategoryViewModel.swift; sourceTree = ""; }; @@ -6219,7 +6218,6 @@ EEA3C2142CA3DB17000E82EC /* DefaultFavoriteProductsUseCaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultFavoriteProductsUseCaseTests.swift; sourceTree = ""; }; EEA3C21D2CA543D9000E82EC /* FavoriteProductsUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteProductsUseCase.swift; sourceTree = ""; }; EEA3C2202CA5440A000E82EC /* MockFavoriteProductsUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockFavoriteProductsUseCase.swift; sourceTree = ""; }; - EEAA45FC293073FE0047D125 /* JetpackInstallStepTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackInstallStepTests.swift; sourceTree = ""; }; EEADF621281A40CB001B40F1 /* ShippingValueLocalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingValueLocalizer.swift; sourceTree = ""; }; EEADF623281A421A001B40F1 /* DefaultShippingValueLocalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShippingValueLocalizer.swift; sourceTree = ""; }; EEADF625281A65A9001B40F1 /* DefaultShippingValueLocalizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShippingValueLocalizerTests.swift; sourceTree = ""; }; @@ -13216,7 +13214,6 @@ DEE183EC292BD900008818AB /* JetpackSetupViewModelTests.swift */, EEC2D27E292CF60E0072132E /* JetpackSetupHostingControllerTests.swift */, EEC2D280292D10520072132E /* SiteCredentialLoginHostingViewControllerTests.swift */, - EEAA45FC293073FE0047D125 /* JetpackInstallStepTests.swift */, ); path = "Jetpack Setup"; sourceTree = ""; @@ -17512,7 +17509,6 @@ 45FBDF2D238BF8BF00127F77 /* AddProductImageCollectionViewCellTests.swift in Sources */, 578195FC25AD1D7C004A5C12 /* OrderFulfillmentUseCaseTests.swift in Sources */, 094C161227B0604700B25F51 /* ProductVariationFormViewModelTests.swift in Sources */, - EEAA45FD293073FE0047D125 /* JetpackInstallStepTests.swift in Sources */, CECC759923D6160000486676 /* AggregateDataHelperTests.swift in Sources */, DE2004642BF744F200660A72 /* ProductStockDashboardCardViewModelTests.swift in Sources */, 454453C82755171E00464AC5 /* HubMenuCoordinatorTests.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackInstallStepTests.swift b/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackInstallStepTests.swift deleted file mode 100644 index 3155fb2213b..00000000000 --- a/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackInstallStepTests.swift +++ /dev/null @@ -1,46 +0,0 @@ -import XCTest -@testable import WooCommerce - -final class JetpackInstallStepTests: XCTestCase { - private let testURL = "https://test.com" - - func test_analyticsDescription_has_correct_key() throws { - // Given - let sut = try XCTUnwrap(JetpackInstallStep.allCases.randomElement()) - - // Then - XCTAssertEqual("jetpack_install_step", sut.analyticsDescription.keys.first) - } - - func test_analyticsDescription_has_correct_value_for_installation_step() throws { - // Given - let sut = JetpackInstallStep.installation - - // Then - XCTAssertEqual("installation", sut.analyticsDescription.values.first) - } - - func test_analyticsDescription_has_correct_value_for_activation_step() throws { - // Given - let sut = JetpackInstallStep.activation - - // Then - XCTAssertEqual("activation", sut.analyticsDescription.values.first) - } - - func test_analyticsDescription_has_correct_value_for_connection_step() throws { - // Given - let sut = JetpackInstallStep.connection - - // Then - XCTAssertEqual("connection", sut.analyticsDescription.values.first) - } - - func test_analyticsDescription_has_correct_value_for_done_step() throws { - // Given - let sut = JetpackInstallStep.done - - // Then - XCTAssertEqual("all_done", sut.analyticsDescription.values.first) - } -} diff --git a/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackSetupHostingControllerTests.swift b/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackSetupHostingControllerTests.swift index c28b5379ea3..4db5dba3cbb 100644 --- a/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackSetupHostingControllerTests.swift +++ b/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackSetupHostingControllerTests.swift @@ -10,25 +10,6 @@ final class JetpackSetupHostingControllerTests: XCTestCase { private let testURL = "https://test.com" private let credentials = Credentials.wpcom(username: "test", authToken: "secret", siteAddress: "https://example.com") - func test_it_tracks_login_jetpack_setup_screen_viewed_when_view_loads_for_unauthenticated_users() throws { - // Given - let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) - let analyticsProvider = MockAnalyticsProvider() - let analytics = WooAnalytics(analyticsProvider: analyticsProvider) - let viewController = JetpackSetupHostingController(siteURL: testURL, - connectionOnly: true, - wpcomCredentials: credentials, - stores: stores, - analytics: analytics, - onStoreNavigation: { _ in }) - - // When - _ = try XCTUnwrap(viewController.view) - - // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_screen_viewed" })) - } - func test_it_tracks_login_jetpack_setup_screen_dismissed_when_view_is_dismissed_for_unauthenticated_users() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) @@ -46,7 +27,11 @@ final class JetpackSetupHostingControllerTests: XCTestCase { let leftBarButtonItem = try XCTUnwrap(viewController.navigationItem.leftBarButtonItem) _ = leftBarButtonItem.target?.perform(leftBarButtonItem.action) + // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_screen_dismissed" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "connection") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["connection_type"] as? String, "native") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["tap"] as? String, "dismiss") } } diff --git a/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackSetupViewModelTests.swift b/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackSetupViewModelTests.swift index a0f208bfa39..55698f88844 100644 --- a/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackSetupViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/Authentication/Jetpack Setup/JetpackSetupViewModelTests.swift @@ -1007,7 +1007,7 @@ final class JetpackSetupViewModelTests: XCTestCase { } // MARK: - Analytics - func test_it_tracks_login_jetpack_setup_go_to_store_button_tapped_when_tapping_go_to_store_button() { + func test_it_tracks_when_tapping_go_to_store_button() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() @@ -1024,11 +1024,11 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.navigateToStore() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_go_to_store_button_tapped" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["tap"] as? String, "go_to_store") } - func test_it_tracks_correct_event_when_jetpack_installation_is_successful() { + func test_it_tracks_correct_event_when_jetpack_installation_starts() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() @@ -1045,8 +1045,6 @@ final class JetpackSetupViewModelTests: XCTestCase { switch action { case .retrieveJetpackPluginDetails(let completion): completion(.failure(error)) - case .installJetpackPlugin(let completion): - completion(.success(())) default: break } @@ -1056,11 +1054,12 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.startSetup() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_install_successful" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "installation") + XCTAssertNil(analyticsProvider.receivedProperties[indexOfEvent]["error_code"]) } - func test_it_tracks_correct_event_when_jetpack_installation_fails() { + func test_it_tracks_correct_event_when_jetpack_installation_fails() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() @@ -1071,14 +1070,13 @@ final class JetpackSetupViewModelTests: XCTestCase { stores: stores, analytics: analytics, delayBeforeRetry: 0) - let error = NetworkError.notFound(response: nil) stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in switch action { case .retrieveJetpackPluginDetails(let completion): - completion(.failure(error)) + completion(.failure(NetworkError.notFound(response: nil))) case .installJetpackPlugin(let completion): - completion(.failure(error)) + completion(.failure(NetworkError.unacceptableStatusCode(statusCode: 403, response: nil))) default: break } @@ -1088,11 +1086,12 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.startSetup() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_install_failed" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "installation") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["error_code"] as? String, "403") } - func test_it_tracks_correct_event_when_jetpack_activation_is_successful() { + func test_it_tracks_correct_event_when_jetpack_activation_starts() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() @@ -1122,11 +1121,12 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.startSetup() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_activation_successful" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "activation") + XCTAssertNil(analyticsProvider.receivedProperties[indexOfEvent]["error_code"]) } - func test_it_tracks_correct_event_when_jetpack_activation_fails() { + func test_it_tracks_correct_event_when_jetpack_activation_fails() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() @@ -1137,16 +1137,13 @@ final class JetpackSetupViewModelTests: XCTestCase { stores: stores, analytics: analytics, delayBeforeRetry: 0) - let error = NetworkError.notFound(response: nil) stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in switch action { case .retrieveJetpackPluginDetails(let completion): - completion(.failure(error)) - case .installJetpackPlugin(let completion): - completion(.success(())) + completion(.success(.fake().copy(status: .inactive))) case .activateJetpackPlugin(let completion): - completion(.failure(error)) + completion(.failure(NetworkError.unacceptableStatusCode(statusCode: 403, response: nil))) default: break } @@ -1155,11 +1152,12 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.startSetup() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_activation_failed" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "activation") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["error_code"] as? String, "403") } - func test_it_tracks_correct_event_when_fetching_jetpack_connection_url_is_successful() throws { + func test_it_tracks_correct_event_when_connection_step_starts() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() @@ -1170,16 +1168,13 @@ final class JetpackSetupViewModelTests: XCTestCase { stores: stores, analytics: analytics, delayBeforeRetry: 0) - let testConnectionURL = try XCTUnwrap(URL(string: "https://test-connection.com")) stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in switch action { case .retrieveJetpackPluginDetails(let completion): completion(.success(.fake())) case .fetchJetpackConnectionData(let completion): - completion(.success(.fake().copy(isRegistered: nil))) - case .fetchJetpackConnectionURL(let completion): - completion(.success((testConnectionURL))) + completion(.success(.fake().copy(isRegistered: true, blogID: 123))) default: break } @@ -1189,11 +1184,12 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.startSetup() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_fetch_jetpack_connection_url_successful" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "connection") + XCTAssertNil(analyticsProvider.receivedProperties[indexOfEvent]["error_code"]) } - func test_it_tracks_correct_event_when_fetching_jetpack_connection_url_fails() { + func test_it_tracks_correct_event_when_connection_step_fails() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() @@ -1210,10 +1206,10 @@ final class JetpackSetupViewModelTests: XCTestCase { case .retrieveJetpackPluginDetails(let completion): completion(.success(.fake())) case .fetchJetpackConnectionData(let completion): - completion(.success(.fake().copy(isRegistered: nil))) - case .fetchJetpackConnectionURL(let completion): - let fetchError = NSError(domain: "Test", code: 1) - completion(.failure(fetchError)) + completion(.success(.fake().copy(isRegistered: true, blogID: 123))) + case .provisionConnection(let completion): + let error = NSError(domain: "Test", code: 1) + completion(.failure(error)) default: break } @@ -1223,57 +1219,63 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.startSetup() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_fetch_jetpack_connection_url_failed" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "connection") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["error_code"] as? String, "1") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["connection_type"] as? String, "native") } - func test_it_tracks_correct_event_when_checking_jetpack_connection_is_successful() throws { + func test_it_tracks_correct_event_when_fetching_jetpack_connection_url_fails() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() let analytics = WooAnalytics(analyticsProvider: analyticsProvider) let viewModel = JetpackSetupViewModel(siteURL: testURL, - connectionOnly: false, + connectionOnly: true, wpcomCredentials: credentials, stores: stores, analytics: analytics, delayBeforeRetry: 0) - let data = JetpackConnectionData.fake().copy( - currentUser: .fake().copy(isConnected: true, wpcomUser: DotcomUser.fake().copy(email: "test@mail.com")) - ) stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in switch action { case .retrieveJetpackPluginDetails(let completion): completion(.success(.fake())) case .fetchJetpackConnectionData(let completion): - completion(.success(data)) + completion(.success(.fake().copy(isRegistered: nil))) + case .fetchJetpackConnectionURL(let completion): + let fetchError = NSError(domain: "Test", code: 1) + completion(.failure(fetchError)) default: break } } // When - viewModel.didAuthorizeJetpackConnection() + viewModel.startSetup() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_all_steps_marked_done" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "connection") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["error_code"] as? String, "1") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["connection_type"] as? String, "web") } - func test_it_tracks_correct_event_when_checking_jetpack_connection_is_successful_but_no_wpCom_user_present() throws { + func test_it_tracks_correct_event_when_checking_jetpack_connection_is_successful() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() let analytics = WooAnalytics(analyticsProvider: analyticsProvider) let viewModel = JetpackSetupViewModel(siteURL: testURL, - connectionOnly: false, + connectionOnly: true, wpcomCredentials: credentials, stores: stores, analytics: analytics, delayBeforeRetry: 0) - let data = JetpackConnectionData.fake().copy(currentUser: .fake().copy(isConnected: true, wpcomUser: nil)) + let data = JetpackConnectionData.fake().copy( + currentUser: .fake().copy(isConnected: true, wpcomUser: DotcomUser.fake().copy(email: "test@mail.com")) + ) stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in switch action { case .fetchJetpackConnectionData(let completion): @@ -1287,45 +1289,54 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.didAuthorizeJetpackConnection() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_cannot_find_WPCOM_user" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "all_done") } - func test_it_tracks_correct_event_when_checking_jetpack_connection_fails() throws { + func test_it_tracks_correct_event_when_checking_jetpack_connection_is_successful_but_no_wpCom_user_present() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() let analytics = WooAnalytics(analyticsProvider: analyticsProvider) let viewModel = JetpackSetupViewModel(siteURL: testURL, - connectionOnly: false, + connectionOnly: true, wpcomCredentials: credentials, stores: stores, analytics: analytics, delayBeforeRetry: 0) - let error = NSError(domain: "Test", code: 1) stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in switch action { case .fetchJetpackConnectionData(let completion): - completion(.failure(error)) + let data = JetpackConnectionData.fake().copy( + currentUser: .fake().copy(isConnected: true, wpcomUser: nil), + isRegistered: true, + blogID: 123 + ) + completion(.success(data)) + case .provisionConnection(let completion): + completion(.success(JetpackConnectionProvisionResponse(userId: 124, scope: "admin", secret: "secret"))) + case let .finalizeConnection(_, _, _, _, completion): + completion(.success(())) default: break } } // When - viewModel.didAuthorizeJetpackConnection() - + viewModel.startSetup() waitUntil { - analyticsProvider.receivedEvents.isNotEmpty + viewModel.setupFailed } // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_error_checking_jetpack_connection" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "connection") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["error_code"] as? String, "99") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["connection_type"] as? String, "native") } - func test_it_tracks_correct_event_when_retrying_setup() { + func test_it_tracks_correct_event_when_retrying_setup() throws { // Given let stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: false)) let analyticsProvider = MockAnalyticsProvider() @@ -1341,7 +1352,10 @@ final class JetpackSetupViewModelTests: XCTestCase { viewModel.retryAllSteps() // Then - XCTAssertNotNil(analyticsProvider.receivedEvents.first(where: { $0 == "login_jetpack_setup_try_again_button_tapped" })) - XCTAssertNil(analyticsProvider.receivedEvents.first(where: { $0 == "jetpack_setup_flow" })) + let indexOfEvent = try XCTUnwrap(analyticsProvider.receivedEvents.lastIndex(where: { $0 == "jetpack_setup_flow" })) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["step"] as? String, "installation") + XCTAssertNil(analyticsProvider.receivedProperties[indexOfEvent]["error_code"]) + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["connection_type"] as? String, "native") + XCTAssertEqual(analyticsProvider.receivedProperties[indexOfEvent]["tap"] as? String, "retry") } }