Skip to content

Commit 98a9f12

Browse files
authored
Merge pull request #7521 from woocommerce/issue/conditional-requests
[Orders Performance] Fire sync requests conditionally
2 parents 5110e2a + 8f57d14 commit 98a9f12

File tree

5 files changed

+113
-29
lines changed

5 files changed

+113
-29
lines changed

WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,12 @@ extension WooAnalyticsEvent {
482482
WooAnalyticsEvent(statName: .collectPaymentTapped,
483483
properties: [:])
484484
}
485+
486+
/// Tracked when accessing the system plugin list without it being in sync.
487+
///
488+
static func pluginsNotSyncedYet() -> WooAnalyticsEvent {
489+
WooAnalyticsEvent(statName: .pluginsNotSyncedYet, properties: [:])
490+
}
485491
}
486492
}
487493

WooCommerce/Classes/Analytics/WooAnalyticsStat.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,9 @@ public enum WooAnalyticsStat: String {
661661
case paymentsMenuCardReadersManualsTapped = "payments_hub_card_readers_manuals_tapped"
662662
case paymentsMenuManageCardReadersTapped = "payments_hub_manage_card_readers_tapped"
663663
case paymentsMenuPaymentProviderTapped = "settings_card_present_select_payment_gateway_tapped"
664+
665+
// MARK: Payments Menu
666+
case pluginsNotSyncedYet = "plugins_not_synced_yet"
664667
}
665668

666669
public extension WooAnalyticsStat {

WooCommerce/Classes/Extensions/SitePlugin+Woo.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ import Yosemite
55
extension SitePlugin {
66
enum SupportedPlugin {
77
public static let WCShip = "WooCommerce Shipping & Tax"
8+
public static let WCTracking = "WooCommerce Shipment Tracking"
89
}
910
}

WooCommerce/Classes/ViewModels/Order Details/OrderDetailsViewModel.swift

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import Experiments
88
import WooFoundation
99
import SwiftUI
1010
import enum Networking.DotcomError
11+
import protocol Storage.StorageManagerType
1112

1213
final class OrderDetailsViewModel {
1314

1415
private let stores: StoresManager
16+
private let storageManager: StorageManagerType
1517
private let currencyFormatter: CurrencyFormatter
1618

1719
private(set) var order: Order
@@ -26,9 +28,11 @@ final class OrderDetailsViewModel {
2628

2729
init(order: Order,
2830
stores: StoresManager = ServiceLocator.stores,
31+
storageManager: StorageManagerType = ServiceLocator.storageManager,
2932
currencyFormatter: CurrencyFormatter = CurrencyFormatter(currencySettings: ServiceLocator.currencySettings)) {
3033
self.order = order
3134
self.stores = stores
35+
self.storageManager = storageManager
3236
self.currencyFormatter = currencyFormatter
3337
}
3438

@@ -265,12 +269,18 @@ extension OrderDetailsViewModel {
265269
}
266270

267271
func syncTrackingsEnablingAddButtonIfReachable(onReloadSections: (() -> ())? = nil, onCompletion: (() -> Void)? = nil) {
268-
syncTracking { [weak self] error in
269-
if error == nil {
270-
self?.trackingIsReachable = true
272+
// If the plugin is not active, there is no point on continuing with a request that will fail.
273+
isPluginActive(SitePlugin.SupportedPlugin.WCTracking) { [weak self] isActive in
274+
guard let self = self, isActive else {
275+
onCompletion?()
276+
return
277+
}
278+
279+
self.trackingIsReachable = true
280+
self.syncTracking { error in
281+
onReloadSections?()
282+
onCompletion?()
271283
}
272-
onReloadSections?()
273-
onCompletion?()
274284
}
275285
}
276286
}
@@ -533,22 +543,31 @@ extension OrderDetailsViewModel {
533543
}
534544

535545
func syncShippingLabels(onCompletion: ((Error?) -> ())? = nil) {
536-
let action = ShippingLabelAction.synchronizeShippingLabels(siteID: order.siteID, orderID: order.orderID) { result in
537-
switch result {
538-
case .success:
539-
ServiceLocator.analytics.track(event: .shippingLabelsAPIRequest(result: .success))
546+
// If the plugin is not active, there is no point on continuing with a request that will fail.
547+
isPluginActive(SitePlugin.SupportedPlugin.WCShip) { [weak self] isActive in
548+
guard let self = self, isActive else {
540549
onCompletion?(nil)
541-
case .failure(let error):
542-
ServiceLocator.analytics.track(event: .shippingLabelsAPIRequest(result: .failed(error: error)))
543-
if error as? DotcomError == .noRestRoute {
544-
DDLogError("⚠️ Endpoint for synchronizing shipping labels is unreachable. WC Shipping plugin may be missing.")
545-
} else {
546-
DDLogError("⛔️ Error synchronizing shipping labels: \(error)")
550+
return
551+
}
552+
553+
let action = ShippingLabelAction.synchronizeShippingLabels(siteID: self.order.siteID, orderID: self.order.orderID) { result in
554+
switch result {
555+
case .success:
556+
ServiceLocator.analytics.track(event: .shippingLabelsAPIRequest(result: .success))
557+
onCompletion?(nil)
558+
case .failure(let error):
559+
ServiceLocator.analytics.track(event: .shippingLabelsAPIRequest(result: .failed(error: error)))
560+
if error as? DotcomError == .noRestRoute {
561+
DDLogError("⚠️ Endpoint for synchronizing shipping labels is unreachable. WC Shipping plugin may be missing.")
562+
} else {
563+
DDLogError("⛔️ Error synchronizing shipping labels: \(error)")
564+
}
565+
onCompletion?(error)
547566
}
548-
onCompletion?(error)
549567
}
568+
self.stores.dispatch(action)
569+
550570
}
551-
stores.dispatch(action)
552571
}
553572

554573
func syncSavedReceipts(onCompletion: ((Error?) -> ())? = nil) {
@@ -566,16 +585,24 @@ extension OrderDetailsViewModel {
566585
}
567586

568587
func checkShippingLabelCreationEligibility(onCompletion: (() -> Void)? = nil) {
569-
let action = ShippingLabelAction.checkCreationEligibility(siteID: order.siteID,
570-
orderID: order.orderID) { [weak self] isEligible in
571-
self?.dataSource.isEligibleForShippingLabelCreation = isEligible
572-
if isEligible, let orderStatus = self?.orderStatus?.status.rawValue {
573-
ServiceLocator.analytics.track(.shippingLabelOrderIsEligible,
574-
withProperties: ["order_status": orderStatus])
588+
// If the plugin is not active, there is no point on continuing with a request that will fail.
589+
isPluginActive(SitePlugin.SupportedPlugin.WCShip) { [weak self] isActive in
590+
guard let self = self, isActive else {
591+
onCompletion?()
592+
return
575593
}
576-
onCompletion?()
594+
595+
let action = ShippingLabelAction.checkCreationEligibility(siteID: self.order.siteID,
596+
orderID: self.order.orderID) { [weak self] isEligible in
597+
self?.dataSource.isEligibleForShippingLabelCreation = isEligible
598+
if isEligible, let orderStatus = self?.orderStatus?.status.rawValue {
599+
ServiceLocator.analytics.track(.shippingLabelOrderIsEligible,
600+
withProperties: ["order_status": orderStatus])
601+
}
602+
onCompletion?()
603+
}
604+
self.stores.dispatch(action)
577605
}
578-
stores.dispatch(action)
579606
}
580607

581608
func checkOrderAddOnFeatureSwitchState(onCompletion: (() -> Void)? = nil) {
@@ -618,6 +645,32 @@ extension OrderDetailsViewModel {
618645

619646
stores.dispatch(deleteTrackingAction)
620647
}
648+
649+
/// Helper function that returns `true` in its callback if the provided plugin name is active on the order's store.
650+
/// Additionally it logs to tracks if the plugin store is accessed without it being in sync so we can handle that edge-case if it happens recurrently.
651+
///
652+
private func isPluginActive(_ plugin: String, completion: @escaping (Bool) -> (Void)) {
653+
guard arePluginsSynced() else {
654+
DDLogError("⚠️ SystemPlugins acceded without being in sync.")
655+
ServiceLocator.analytics.track(event: WooAnalyticsEvent.Orders.pluginsNotSyncedYet())
656+
return completion(false)
657+
}
658+
659+
let action = SystemStatusAction.fetchSystemPlugin(siteID: order.siteID, systemPluginName: plugin) { plugin in
660+
completion(plugin?.active == true)
661+
}
662+
stores.dispatch(action)
663+
}
664+
665+
/// Function that checks for any existing system plugin in the order's store.
666+
/// If there is none, we assume plugins are not synced because at least the`WooCommerce` plugin should be present.
667+
///
668+
private func arePluginsSynced() -> Bool {
669+
let predicate = NSPredicate(format: "siteID == %lld", order.siteID)
670+
let resultsController = ResultsController<StorageSystemPlugin>(storageManager: storageManager, matching: predicate, sortedBy: [])
671+
try? resultsController.performFetch()
672+
return !resultsController.isEmpty
673+
}
621674
}
622675

623676
// MARK: Definitions

WooCommerce/WooCommerceTests/ViewRelated/OrderDetailsViewModelTests.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ final class OrderDetailsViewModelTests: XCTestCase {
99
private var viewModel: OrderDetailsViewModel!
1010

1111
private var storesManager: MockStoresManager!
12+
private var storageManager: MockStorageManager!
1213

1314
override func setUp() {
1415
storesManager = MockStoresManager(sessionManager: SessionManager.makeForTesting())
16+
storageManager = MockStorageManager()
1517

1618
order = MockOrders().sampleOrder()
1719

18-
viewModel = OrderDetailsViewModel(order: order, stores: storesManager)
20+
viewModel = OrderDetailsViewModel(order: order, stores: storesManager, storageManager: storageManager)
1921

2022
let analytics = WooAnalytics(analyticsProvider: MockAnalyticsProvider())
2123
ServiceLocator.setAnalytics(analytics)
@@ -73,16 +75,35 @@ final class OrderDetailsViewModelTests: XCTestCase {
7375

7476
func test_checkShippingLabelCreationEligibility_dispatches_correctly() throws {
7577
// Given
78+
79+
// Make sure the are plugins synced
80+
let plugin = SystemPlugin.fake().copy(siteID: order.siteID, name: SitePlugin.SupportedPlugin.WCShip, active: true)
81+
storageManager.insertSampleSystemPlugin(readOnlySystemPlugin: plugin)
82+
7683
storesManager.reset()
7784
XCTAssertEqual(storesManager.receivedActions.count, 0)
7885

7986
// When
80-
viewModel.checkShippingLabelCreationEligibility()
87+
waitForExpectation { exp in
88+
89+
// Return the active WCShip plugin.
90+
storesManager.whenReceivingAction(ofType: SystemStatusAction.self) { action in
91+
switch action {
92+
case .fetchSystemPlugin(_, _, let onCompletion):
93+
onCompletion(plugin)
94+
exp.fulfill()
95+
default:
96+
break
97+
}
98+
}
99+
100+
viewModel.checkShippingLabelCreationEligibility()
101+
}
81102

82103
// Then
83-
XCTAssertEqual(storesManager.receivedActions.count, 1)
104+
XCTAssertEqual(storesManager.receivedActions.count, 2)
84105

85-
let action = try XCTUnwrap(storesManager.receivedActions.first as? ShippingLabelAction)
106+
let action = try XCTUnwrap(storesManager.receivedActions.last as? ShippingLabelAction)
86107
guard case let ShippingLabelAction.checkCreationEligibility(siteID: siteID,
87108
orderID: orderID,
88109
onCompletion: _) = action else {

0 commit comments

Comments
 (0)