Skip to content

Commit 04a683c

Browse files
authored
Merge pull request #5366 from woocommerce/issue/5300-card-reader-software-update-tracks-events
Add card reader software update tracks events
2 parents 49573f4 + a785415 commit 04a683c

File tree

5 files changed

+162
-44
lines changed

5 files changed

+162
-44
lines changed

Hardware/Hardware/CardReader/StripeCardReader/StripeCardReaderService.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -499,10 +499,10 @@ extension StripeCardReaderService: BluetoothReaderDelegate {
499499

500500
public func reader(_ reader: Reader, didFinishInstallingUpdate update: ReaderSoftwareUpdate?, error: Error?) {
501501
if let error = error {
502-
let underlyingError = UnderlyingError(with: error)
503-
if underlyingError != .commandCancelled {
504-
softwareUpdateSubject.send(.failed(error: error))
505-
}
502+
softwareUpdateSubject.send(.failed(
503+
error: CardReaderServiceError.softwareUpdate(underlyingError: UnderlyingError(with: error),
504+
batteryLevel: reader.batteryLevel?.doubleValue))
505+
)
506506
softwareUpdateSubject.send(.available)
507507
} else {
508508
softwareUpdateSubject.send(.completed)

WooCommerce/Classes/Analytics/WooAnalyticsStat.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@ public enum WooAnalyticsStat: String {
155155
case cardReaderConnectionSuccess = "card_reader_connection_success"
156156
case cardReaderDisconnectTapped = "card_reader_disconnect_tapped"
157157

158+
// MARK: Card Reader Software Update Events
159+
//
160+
case cardReaderSoftwareUpdateTapped = "card_reader_software_update_tapped"
161+
case cardReaderSoftwareUpdateStarted = "card_reader_software_update_started"
162+
case cardReaderSoftwareUpdateSuccess = "card_reader_software_update_success"
163+
case cardReaderSoftwareUpdateFailed = "card_reader_software_update_failed"
164+
case cardReaderSoftwareUpdateCancelTapped = "card_reader_software_update_cancel_tapped"
165+
case cardReaderSoftwareUpdateCanceled = "card_reader_software_update_canceled"
166+
158167
// MARK: Card-Present Payments Onboarding
159168
case cardPresentOnboardingLearnMoreTapped = "card_present_onboarding_learn_more_tapped"
160169
case cardPresentOnboardingNotCompleted = "card_present_onboarding_not_completed"

WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/CardReaderSettingsConnectedViewModel.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,22 @@ final class CardReaderSettingsConnectedViewModel: CardReaderSettingsPresentedVie
5959
self.readerUpdateError = nil
6060
self.softwareUpdateCancelable = cancelable
6161
self.readerUpdateProgress = 0
62+
ServiceLocator.analytics.track(.cardReaderSoftwareUpdateStarted)
6263
case .installing(progress: let progress):
6364
self.readerUpdateProgress = progress
6465
case .failed(error: let error):
66+
if case CardReaderServiceError.softwareUpdate(underlyingError: let underlyingError, batteryLevel: _) = error,
67+
underlyingError == .readerSoftwareUpdateFailedInterrupted {
68+
// Update was cancelled, don't treat this as an error
69+
break
70+
}
6571
self.readerUpdateError = error
6672
self.completeCardReaderUpdate(success: false)
73+
ServiceLocator.analytics.track(.cardReaderSoftwareUpdateFailed)
6774
case .completed:
6875
self.readerUpdateProgress = 1
6976
self.softwareUpdateCancelable = nil
77+
ServiceLocator.analytics.track(.cardReaderSoftwareUpdateSuccess)
7078
// If we were installing a software update, introduce a small delay so the user can
7179
// actually see a success message showing the installation was complete
7280
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { [weak self] in
@@ -117,16 +125,19 @@ final class CardReaderSettingsConnectedViewModel: CardReaderSettingsPresentedVie
117125
/// Allows the view controller to kick off a card reader update
118126
///
119127
func startCardReaderUpdate() {
128+
ServiceLocator.analytics.track(.cardReaderSoftwareUpdateTapped)
120129
let action = CardPresentPaymentAction.startCardReaderUpdate
121130
ServiceLocator.stores.dispatch(action)
122131
}
123132

124133
func cancelCardReaderUpdate() {
134+
ServiceLocator.analytics.track(.cardReaderSoftwareUpdateCancelTapped)
125135
softwareUpdateCancelable?.cancel(completion: { [weak self] result in
126136
if case .failure(let error) = result {
127137
print("=== error canceling software update: \(error)")
128138
} else {
129139
self?.completeCardReaderUpdate(success: false)
140+
ServiceLocator.analytics.track(.cardReaderSoftwareUpdateCanceled)
130141
}
131142
})
132143
}

WooCommerce/WooCommerceTests/Mocks/MockCardPresentPaymentsStoresManager.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,29 @@ extension MockCardPresentPaymentsStoresManager {
8686
case connectionFailure
8787
}
8888
}
89+
90+
extension MockCardPresentPaymentsStoresManager {
91+
func simulateSuccessfulUpdate() {
92+
softwareUpdateSubject.send(.completed)
93+
}
94+
95+
func simulateFailedUpdate(error: Error) {
96+
softwareUpdateSubject.send(.failed(error: error))
97+
}
98+
99+
func simulateCancelableUpdate(onCancel: @escaping () -> Void) {
100+
softwareUpdateSubject.send(.started(cancelable: MockFallibleCancelable(onCancel: {
101+
onCancel()
102+
self.softwareUpdateSubject.send(
103+
.failed(error: CardReaderServiceError.softwareUpdate(
104+
underlyingError: .readerSoftwareUpdateFailedInterrupted,
105+
batteryLevel: 0.86
106+
))
107+
)
108+
})))
109+
}
110+
111+
func simulateUpdateStarted() {
112+
softwareUpdateSubject.send(.started(cancelable: nil))
113+
}
114+
}

WooCommerce/WooCommerceTests/ViewRelated/Dashboard/CardReaderSettings/CardReaderSettingsConnectedViewModelTests.swift

Lines changed: 112 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,27 @@ import XCTest
33
@testable import WooCommerce
44

55
final class CardReaderSettingsConnectedViewModelTests: XCTestCase {
6+
private var mockStoresManager: MockCardPresentPaymentsStoresManager!
7+
private var analytics: MockAnalyticsProvider!
8+
9+
private var viewModel: CardReaderSettingsConnectedViewModel!
10+
11+
override func setUpWithError() throws {
12+
mockStoresManager = MockCardPresentPaymentsStoresManager(
13+
connectedReaders: [MockCardReader.bbposChipper2XBT()],
14+
discoveredReaders: [],
15+
sessionManager: SessionManager.testingInstance
16+
)
17+
ServiceLocator.setStores(mockStoresManager)
18+
19+
analytics = MockAnalyticsProvider()
20+
ServiceLocator.setAnalytics(WooAnalytics(analyticsProvider: analytics))
21+
22+
viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
23+
}
624

725
func test_did_change_should_show_returns_false_if_no_connected_readers() {
8-
let mockStoresManager = MockCardPresentPaymentsStoresManager(
26+
mockStoresManager = MockCardPresentPaymentsStoresManager(
927
connectedReaders: [],
1028
discoveredReaders: [],
1129
sessionManager: SessionManager.testingInstance
@@ -22,16 +40,9 @@ final class CardReaderSettingsConnectedViewModelTests: XCTestCase {
2240
}
2341

2442
func test_did_change_should_show_returns_true_if_a_reader_is_connected() {
25-
let mockStoresManager = MockCardPresentPaymentsStoresManager(
26-
connectedReaders: [MockCardReader.bbposChipper2XBT()],
27-
discoveredReaders: [],
28-
sessionManager: SessionManager.testingInstance
29-
)
30-
ServiceLocator.setStores(mockStoresManager)
31-
3243
let expectation = self.expectation(description: #function)
3344

34-
let _ = CardReaderSettingsConnectedViewModel(didChangeShouldShow: { shouldShow in
45+
viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: { shouldShow in
3546
XCTAssertTrue(shouldShow == .isTrue)
3647
expectation.fulfill()
3748
} )
@@ -40,14 +51,7 @@ final class CardReaderSettingsConnectedViewModelTests: XCTestCase {
4051
}
4152

4253
func test_view_model_correctly_formats_connected_card_reader_battery_level() {
43-
let mockStoresManager = MockCardPresentPaymentsStoresManager(
44-
connectedReaders: [MockCardReader.bbposChipper2XBT()],
45-
discoveredReaders: [],
46-
sessionManager: SessionManager.testingInstance
47-
)
48-
ServiceLocator.setStores(mockStoresManager)
49-
50-
let viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
54+
viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
5155
XCTAssertEqual(viewModel.connectedReaderBatteryLevel, "50% Battery")
5256
}
5357

@@ -59,18 +63,11 @@ final class CardReaderSettingsConnectedViewModelTests: XCTestCase {
5963
)
6064
ServiceLocator.setStores(mockStoresManager)
6165

62-
let viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
66+
viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
6367
XCTAssertEqual(viewModel.connectedReaderBatteryLevel, "Unknown Battery Level")
6468
}
6569

6670
func test_view_model_correctly_formats_connected_card_reader_software_version() {
67-
let mockStoresManager = MockCardPresentPaymentsStoresManager(
68-
connectedReaders: [MockCardReader.bbposChipper2XBT()],
69-
discoveredReaders: [],
70-
sessionManager: SessionManager.testingInstance
71-
)
72-
ServiceLocator.setStores(mockStoresManager)
73-
7471
let viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
7572
XCTAssertEqual(viewModel.connectedReaderSoftwareVersion, "Version: 1.00.03.34-SZZZ_Generic_v45-300001")
7673
}
@@ -83,34 +80,25 @@ final class CardReaderSettingsConnectedViewModelTests: XCTestCase {
8380
)
8481
ServiceLocator.setStores(mockStoresManager)
8582

86-
let viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
83+
viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
8784
XCTAssertEqual(viewModel.connectedReaderSoftwareVersion, "Unknown Software Version")
8885
}
8986

9087
func test_startCardReaderUpdate_properly_handles_successful_update() {
9188
// Given
9289
let expectation = self.expectation(description: #function)
9390

94-
let mockStoresManager = MockCardPresentPaymentsStoresManager(
95-
connectedReaders: [MockCardReader.bbposChipper2XBT()],
96-
discoveredReaders: [],
97-
sessionManager: SessionManager.testingInstance
98-
)
99-
ServiceLocator.setStores(mockStoresManager)
100-
101-
let viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
102-
10391
var updateDidBegin = false
10492

10593
viewModel.didUpdate = {
106-
if viewModel.readerUpdateInProgress {
94+
if self.viewModel.readerUpdateInProgress {
10795
updateDidBegin = true
10896
}
10997

11098
// Update began
11199
if updateDidBegin {
112100
// But now it has stopped
113-
if !viewModel.readerUpdateInProgress {
101+
if !self.viewModel.readerUpdateInProgress {
114102
expectation.fulfill()
115103
}
116104
}
@@ -136,19 +124,19 @@ final class CardReaderSettingsConnectedViewModelTests: XCTestCase {
136124
)
137125
ServiceLocator.setStores(mockStoresManager)
138126

139-
let viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
127+
viewModel = CardReaderSettingsConnectedViewModel(didChangeShouldShow: nil)
140128

141129
var updateDidBegin = false
142130

143131
viewModel.didUpdate = {
144-
if viewModel.readerUpdateInProgress {
132+
if self.viewModel.readerUpdateInProgress {
145133
updateDidBegin = true
146134
}
147135

148136
// Update began
149137
if updateDidBegin {
150138
// But now it has stopped
151-
if !viewModel.readerUpdateInProgress {
139+
if !self.viewModel.readerUpdateInProgress {
152140
expectation.fulfill()
153141
}
154142
}
@@ -160,4 +148,88 @@ final class CardReaderSettingsConnectedViewModelTests: XCTestCase {
160148
// Then
161149
wait(for: [expectation], timeout: Constants.expectationTimeout)
162150
}
151+
152+
func test_startCardReaderUpdate_ViewModel_LogsTracksEvent_cardReaderSoftwareUpdateTapped() {
153+
// Given
154+
155+
// When
156+
viewModel.startCardReaderUpdate()
157+
158+
// Then
159+
XCTAssert(analytics.receivedEvents.contains(WooAnalyticsStat.cardReaderSoftwareUpdateTapped.rawValue))
160+
}
161+
162+
func test_startCardReaderUpdate_ViewModel_LogsTracksEvent_cardReaderSoftwareUpdateStarted() {
163+
// Given
164+
165+
// When
166+
mockStoresManager.simulateUpdateStarted()
167+
168+
// Then
169+
XCTAssert(analytics.receivedEvents.contains(WooAnalyticsStat.cardReaderSoftwareUpdateStarted.rawValue))
170+
}
171+
172+
func test_WhenStoreSendsUpdateComplete_ViewModel_LogsTracksEvent_cardReaderSoftwareUpdateSuccess() {
173+
// Given
174+
175+
// When
176+
mockStoresManager.simulateSuccessfulUpdate()
177+
178+
// Then
179+
XCTAssert(analytics.receivedEvents.contains(WooAnalyticsStat.cardReaderSoftwareUpdateSuccess.rawValue))
180+
}
181+
182+
func test_WhenStoreSendsUpdateFailed_ViewModel_LogsTracksEvent_cardReaderSoftwareUpdateFailed() {
183+
// Given
184+
185+
// When
186+
let expectedError = CardReaderServiceError.softwareUpdate(underlyingError: .readerSoftwareUpdateFailedBatteryLow,
187+
batteryLevel: 0.4)
188+
mockStoresManager.simulateFailedUpdate(error: expectedError)
189+
190+
// Then
191+
XCTAssert(analytics.receivedEvents.contains(WooAnalyticsStat.cardReaderSoftwareUpdateFailed.rawValue))
192+
}
193+
194+
func test_WhenUserCancelsUpdate_ViewModel_LogsTracksEvent_cardReaderSoftwareUpdateCancelTapped() {
195+
// Given
196+
197+
// When
198+
viewModel.cancelCardReaderUpdate()
199+
200+
// Then
201+
XCTAssert(analytics.receivedEvents.contains(WooAnalyticsStat.cardReaderSoftwareUpdateCancelTapped.rawValue))
202+
}
203+
204+
func test_WhenUpdateIsSuccessfullyCanceled_ViewModel_LogsTracksEvent_cardReaderSoftwareUpdateCanceled() {
205+
// Given
206+
let expectation = self.expectation(description: #function)
207+
208+
mockStoresManager.simulateCancelableUpdate {
209+
expectation.fulfill()
210+
}
211+
212+
// When
213+
viewModel.cancelCardReaderUpdate()
214+
215+
// Then
216+
wait(for: [expectation], timeout: Constants.expectationTimeout)
217+
XCTAssert(analytics.receivedEvents.contains(WooAnalyticsStat.cardReaderSoftwareUpdateCanceled.rawValue))
218+
}
219+
220+
func test_WhenUpdateIsSuccessfullyCanceled_ViewModel_DoesNotLogTracksEvent_cardReaderSoftwareUpdateFailed() {
221+
// Given
222+
let expectation = self.expectation(description: #function)
223+
224+
mockStoresManager.simulateCancelableUpdate {
225+
expectation.fulfill()
226+
}
227+
228+
// When
229+
viewModel.cancelCardReaderUpdate()
230+
231+
// Then
232+
wait(for: [expectation], timeout: Constants.expectationTimeout)
233+
XCTAssertFalse(analytics.receivedEvents.contains(WooAnalyticsStat.cardReaderSoftwareUpdateFailed.rawValue))
234+
}
163235
}

0 commit comments

Comments
 (0)