Skip to content

Commit 32ea485

Browse files
committed
Merge branch 'develop' into issue/121-manual-status-change
2 parents 44f1993 + b49abc0 commit 32ea485

File tree

25 files changed

+425
-76
lines changed

25 files changed

+425
-76
lines changed

Networking/NetworkingTests/Remote/ReportRemoteTests.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class ReportRemoteTests: XCTestCase {
2121
network.removeAllSimulatedResponses()
2222
}
2323

24+
// MARK: - loadOrderTotals
25+
2426
/// Verifies that 'loadOrderTotals' properly parses the successful response
2527
///
2628
func testOrderTotalsReturnsSuccess() {
@@ -65,4 +67,43 @@ class ReportRemoteTests: XCTestCase {
6567
}
6668
wait(for: [expectation], timeout: Constants.expectationTimeout)
6769
}
70+
71+
// MARK: - loadOrderStatuses
72+
73+
/// Verifies that 'loadOrderStatuses' properly parses the successful response
74+
///
75+
func testLoadOrderStatusesReturnsSuccess() {
76+
let expectation = self.expectation(description: "Load order statuses")
77+
let remote = ReportRemote(network: network)
78+
79+
network.simulateResponse(requestUrlSuffix: "reports/orders/totals", filename: "report-orders")
80+
remote.loadOrderStatuses(for: sampleSiteID) { (orderStatuses, error) in
81+
XCTAssertNil(error)
82+
XCTAssertNotNil(orderStatuses)
83+
XCTAssertEqual(orderStatuses?.count, 9)
84+
expectation.fulfill()
85+
}
86+
wait(for: [expectation], timeout: Constants.expectationTimeout)
87+
}
88+
89+
/// Verifies that `loadOrderStatuses` correctly returns a Dotcom Error, whenever the request failed.
90+
///
91+
func testLoadOrderStatusesProperlyParsesErrorResponses() {
92+
let expectation = self.expectation(description: "Error Handling")
93+
let remote = ReportRemote(network: network)
94+
95+
network.simulateResponse(requestUrlSuffix: "reports/orders/totals", filename: "generic_error")
96+
remote.loadOrderTotals(for: sampleSiteID) { (reportTotals, error) in
97+
guard let error = error as? DotcomError else {
98+
XCTFail()
99+
return
100+
}
101+
102+
XCTAssert(error == .unauthorized)
103+
XCTAssertEqual(reportTotals?.isEmpty, true)
104+
105+
expectation.fulfill()
106+
}
107+
wait(for: [expectation], timeout: Constants.expectationTimeout)
108+
}
68109
}

WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewController.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,6 @@ private extension DashboardViewController {
220220
group.leave()
221221
}
222222

223-
group.enter()
224-
newOrdersViewController.syncOrderStatus() { _ in
225-
// don't bubble up error to user
226-
group.leave()
227-
}
228-
229223
group.enter()
230224
topPerformersViewController.syncTopPerformers() { error in
231225
if let error = error {

WooCommerce/Classes/ViewRelated/Dashboard/MyStore/NewOrdersViewController.swift

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ class NewOrdersViewController: UIViewController {
1818
@IBOutlet private weak var actionButton: UIButton!
1919
@IBOutlet private weak var bottomSpacerView: UIView!
2020

21-
private var orderStatuses: [OrderStatus]?
21+
private var orderStatuses: [OrderStatus]? {
22+
didSet {
23+
updateNewOrdersIfNeeded(orderCount: totalPendingOrders)
24+
}
25+
}
26+
27+
/// Total number of pending server-side orders
28+
///
29+
private var totalPendingOrders: Int {
30+
return orderStatuses?.filter({ $0.slug == OrderStatusEnum.processing.rawValue }).map({ $0.total }).reduce(0, +) ?? 0
31+
}
2232

2333
// MARK: - Public Properties
2434

@@ -47,35 +57,12 @@ extension NewOrdersViewController {
4757
return
4858
}
4959

50-
let action = StatsAction.retrieveOrderTotals(siteID: siteID, status: .processing) { [weak self] (processingOrderCount, error) in
51-
guard let `self` = self, let processingOrderCount = processingOrderCount else {
52-
if let error = error {
53-
DDLogError("⛔️ Dashboard (New Orders) — Error synchronizing pending orders: \(error)")
54-
}
55-
onCompletion?(error)
56-
return
57-
}
58-
59-
self.updateNewOrdersIfNeeded(orderCount: processingOrderCount)
60-
onCompletion?(nil)
61-
}
62-
63-
StoresManager.shared.dispatch(action)
64-
}
65-
66-
func syncOrderStatus(onCompletion: ((Error?) -> Void)? = nil) {
67-
guard let siteID = StoresManager.shared.sessionManager.defaultStoreID else {
68-
onCompletion?(nil)
69-
return
70-
}
71-
7260
let action = OrderStatusAction.retrieveOrderStatuses(siteID: siteID) { [weak self] (orderStatuses, error) in
7361
if let error = error {
7462
DDLogError("⛔️ Dashboard (New Orders) — Error synchronizing order statuses: \(error)")
7563
}
7664

7765
self?.orderStatuses = orderStatuses
78-
7966
onCompletion?(error)
8067
}
8168

@@ -110,18 +97,14 @@ private extension NewOrdersViewController {
11097
private extension NewOrdersViewController {
11198

11299
@IBAction func buttonTouchUpInside(_ sender: UIButton) {
113-
sender.fadeOutSelectedBackground {
114-
WooAnalytics.shared.track(.dashboardNewOrdersButtonTapped)
115-
116-
guard let statuses = self.orderStatuses else {
117-
DDLogError("Error: missing list of order statuses. Cannot present new orders list.")
118-
return
119-
}
120-
121-
for filterStatus in statuses where filterStatus.slug == OrderStatusEnum.processing.rawValue {
122-
MainTabBarController.presentOrders(statusFilter: filterStatus)
123-
}
100+
sender.fadeOutSelectedBackground()
101+
guard let pendingStatus = orderStatuses?.first(where: { $0.slug == OrderStatusEnum.processing.rawValue }) else {
102+
DDLogError("⛔️ Unable to display pending orders list — unable to locate pending status for current site.")
103+
return
124104
}
105+
106+
WooAnalytics.shared.track(.dashboardNewOrdersButtonTapped)
107+
MainTabBarController.presentOrders(statusFilter: pendingStatus)
125108
}
126109

127110
@IBAction func buttonTouchUpOutside(_ sender: UIButton) {

WooCommerce/Classes/ViewRelated/Dashboard/Settings/Help/ApplicationLogViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ private extension ApplicationLogViewController {
216216
cell.selectionStyle = .default
217217
cell.textLabel?.textAlignment = .center
218218
cell.textLabel?.textColor = StyleManager.destructiveActionColor
219-
cell.textLabel?.text = NSLocalizedString("Clear old activity logs", comment: "Deletes all activity logs except for the marked 'Current'.")
219+
cell.textLabel?.text = NSLocalizedString("Reset Activity Log", comment: "Deletes all activity logs except for the marked 'Current'.")
220220
}
221221
}
222222

WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,13 @@ class OrdersViewController: UIViewController {
3636
return ResultsController<StorageOrder>(storageManager: storageManager, sectionNameKeyPath: "normalizedAgeAsString", sortedBy: [descriptor])
3737
}()
3838

39-
/// ResultsController: Surrounds us. Binds the galaxy together. And also, keeps the UITableView <> (Stored) OrderStatuses in sync.
39+
/// ResultsController: Handles all things order status
4040
///
4141
private lazy var statusResultsController: ResultsController<StorageOrderStatus> = {
4242
let storageManager = AppDelegate.shared.storageManager
43-
let predicate = NSPredicate(format: "siteID == %lld", StoresManager.shared.sessionManager.defaultStoreID ?? Int.min)
4443
let descriptor = NSSortDescriptor(key: "slug", ascending: true)
4544

46-
return ResultsController<StorageOrderStatus>(storageManager: storageManager, matching: predicate, sortedBy: [descriptor])
45+
return ResultsController<StorageOrderStatus>(storageManager: storageManager, sortedBy: [descriptor])
4746
}()
4847

4948
/// SyncCoordinator: Keeps tracks of which pages have been refreshed, and encapsulates the "What should we sync now" logic.
@@ -66,6 +65,12 @@ class OrdersViewController: UIViewController {
6665
}
6766
}
6867

68+
/// The current list of order statuses for the default site
69+
///
70+
private var currentSiteStatuses: [OrderStatus] {
71+
return statusResultsController.fetchedObjects
72+
}
73+
6974
/// Keep track of the (Autosizing Cell's) Height. This helps us prevent UI flickers, due to sizing recalculations.
7075
///
7176
private var estimatedRowHeights = [IndexPath: CGFloat]()
@@ -118,19 +123,22 @@ class OrdersViewController: UIViewController {
118123

119124
refreshTitle()
120125
refreshResultsPredicate()
126+
refreshStatusPredicate()
121127
registerTableViewCells()
122128

123129
configureSyncingCoordinator()
124130
configureNavigation()
125131
configureTableView()
126-
configureResultsController()
132+
configureResultsControllers()
127133

128134
startListeningToNotifications()
129135
}
130136

131137
override func viewWillAppear(_ animated: Bool) {
132138
super.viewWillAppear(animated)
133139

140+
syncOrderStatus()
141+
resetStatusFilterIfNeeded()
134142
syncingCoordinator.synchronizeFirstPage()
135143
if AppRatingManager.shared.shouldPromptForAppReview() {
136144
displayRatingPrompt()
@@ -164,7 +172,7 @@ private extension OrdersViewController {
164172
navigationItem.title = title
165173
}
166174

167-
/// Setup: Filtering
175+
/// Setup: Order filtering
168176
///
169177
func refreshResultsPredicate() {
170178
resultsController.predicate = {
@@ -184,6 +192,12 @@ private extension OrdersViewController {
184192
tableView.reloadData()
185193
}
186194

195+
/// Setup: Order status predicate
196+
///
197+
func refreshStatusPredicate() {
198+
statusResultsController.predicate = NSPredicate(format: "siteID == %lld", StoresManager.shared.sessionManager.defaultStoreID ?? Int.min)
199+
}
200+
187201
/// Setup: Navigation Item
188202
///
189203
func configureNavigation() {
@@ -225,9 +239,12 @@ private extension OrdersViewController {
225239

226240
/// Setup: Results Controller
227241
///
228-
func configureResultsController() {
242+
func configureResultsControllers() {
243+
// Orders FRC
229244
resultsController.startForwardingEvents(to: tableView)
230245
try? resultsController.performFetch()
246+
247+
// Order status FRC
231248
try? statusResultsController.performFetch()
232249
}
233250

@@ -285,6 +302,8 @@ extension OrdersViewController {
285302
/// Runs whenever the default Account is updated.
286303
///
287304
@objc func defaultAccountWasUpdated() {
305+
statusFilter = nil
306+
refreshStatusPredicate()
288307
syncingCoordinator.resetInternalState()
289308
}
290309
}
@@ -316,7 +335,7 @@ extension OrdersViewController {
316335
self?.statusFilter = nil
317336
}
318337

319-
for orderStatus in statusResultsController.fetchedObjects {
338+
for orderStatus in currentSiteStatuses {
320339
actionSheet.addDefaultActionWithTitle(orderStatus.name) { [weak self] _ in
321340
self?.statusFilter = orderStatus
322341
}
@@ -331,6 +350,7 @@ extension OrdersViewController {
331350

332351
@IBAction func pullToRefresh(sender: UIRefreshControl) {
333352
WooAnalytics.shared.track(.ordersListPulledToRefresh)
353+
syncOrderStatus()
334354
syncingCoordinator.synchronizeFirstPage {
335355
sender.endRefreshing()
336356
}
@@ -373,6 +393,24 @@ private extension OrdersViewController {
373393
let action = OrderAction.resetStoredOrders(onCompletion: onCompletion)
374394
StoresManager.shared.dispatch(action)
375395
}
396+
397+
/// Reset the current status filter if needed (e.g. when changing stores and the currently
398+
/// selected filter does not exist in the new store)
399+
///
400+
func resetStatusFilterIfNeeded() {
401+
guard let statusFilter = statusFilter else {
402+
// "All" is the current filter so bail
403+
return
404+
}
405+
guard currentSiteStatuses.isEmpty == false else {
406+
self.statusFilter = nil
407+
return
408+
}
409+
410+
if currentSiteStatuses.contains(statusFilter) == false {
411+
self.statusFilter = nil
412+
}
413+
}
376414
}
377415

378416

@@ -411,6 +449,26 @@ extension OrdersViewController: SyncingCoordinatorDelegate {
411449

412450
StoresManager.shared.dispatch(action)
413451
}
452+
453+
func syncOrderStatus(onCompletion: ((Error?) -> Void)? = nil) {
454+
guard let siteID = StoresManager.shared.sessionManager.defaultStoreID else {
455+
onCompletion?(nil)
456+
return
457+
}
458+
459+
// First, let's verify our FRC predicate is up to date
460+
refreshStatusPredicate()
461+
462+
let action = OrderStatusAction.retrieveOrderStatuses(siteID: siteID) { [weak self] (_, error) in
463+
if let error = error {
464+
DDLogError("⛔️ Order List — Error synchronizing order statuses: \(error)")
465+
}
466+
self?.resetStatusFilterIfNeeded()
467+
onCompletion?(error)
468+
}
469+
470+
StoresManager.shared.dispatch(action)
471+
}
414472
}
415473

416474

@@ -472,6 +530,7 @@ private extension OrdersViewController {
472530
let message = NSLocalizedString("Unable to refresh list", comment: "Refresh Action Failed")
473531
let actionTitle = NSLocalizedString("Retry", comment: "Retry Action")
474532
let notice = Notice(title: message, feedbackType: .error, actionTitle: actionTitle) { [weak self] in
533+
self?.syncOrderStatus()
475534
self?.sync(pageNumber: pageNumber, pageSize: pageSize)
476535
}
477536

@@ -557,8 +616,7 @@ private extension OrdersViewController {
557616
}
558617

559618
func lookUpOrderStatus(for order: Order) -> OrderStatus? {
560-
let listAll = statusResultsController.fetchedObjects
561-
for orderStatus in listAll where orderStatus.slug == order.statusKey {
619+
for orderStatus in currentSiteStatuses where orderStatus.slug == order.statusKey {
562620
return orderStatus
563621
}
564622

WooCommerce/Resources/ar.lproj/Localizable.strings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
"Certificate error" = "خطأ في الشهادة";
136136

137137
/* Deletes all activity logs except for the marked 'Current'. */
138-
"Clear old activity logs" = "مسح سجلات النشاط القديمة";
138+
"Reset Activity Log" = "مسح سجلات النشاط القديمة";
139139

140140
/* Settings > Privacy Settings > collect info section. Label for the `Collect Information` toggle. */
141141
"Collect Information" = "جمع المعلومات";

WooCommerce/Resources/de.lproj/Localizable.strings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
"Certificate error" = "Zertifikatsfehler";
136136

137137
/* Deletes all activity logs except for the marked 'Current'. */
138-
"Clear old activity logs" = "Alte Aktivitätsprotokolle löschen";
138+
"Reset Activity Log" = "Alte Aktivitätsprotokolle löschen";
139139

140140
/* Settings > Privacy Settings > collect info section. Label for the `Collect Information` toggle. */
141141
"Collect Information" = "Informationen erfassen";

WooCommerce/Resources/en.lproj/Localizable.strings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
"Certificate error" = "Certificate error";
134134

135135
/* Deletes all activity logs except for the marked 'Current'. */
136-
"Clear old activity logs" = "Clear old activity logs";
136+
"Reset Activity Log" = "Reset Activity Log";
137137

138138
/* Settings > Privacy Settings > collect info section. Label for the `Collect Information` toggle. */
139139
"Collect Information" = "Collect Information";

WooCommerce/Resources/es.lproj/Localizable.strings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
"Certificate error" = "Error del certificado";
136136

137137
/* Deletes all activity logs except for the marked 'Current'. */
138-
"Clear old activity logs" = "Borrar registros de actividad antiguos";
138+
"Reset Activity Log" = "Borrar registros de actividad antiguos";
139139

140140
/* Settings > Privacy Settings > collect info section. Label for the `Collect Information` toggle. */
141141
"Collect Information" = "Recopilar información";

WooCommerce/Resources/fr.lproj/Localizable.strings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
"Certificate error" = "Erreur de certificat";
136136

137137
/* Deletes all activity logs except for the marked 'Current'. */
138-
"Clear old activity logs" = "Effacer vos anciens journaux d'activités";
138+
"Reset Activity Log" = "Effacer vos anciens journaux d'activités";
139139

140140
/* Settings > Privacy Settings > collect info section. Label for the `Collect Information` toggle. */
141141
"Collect Information" = "Recueil d’information";

0 commit comments

Comments
 (0)