Skip to content

Commit ae9c46e

Browse files
authored
Merge pull request #7351 from woocommerce/feat/7318-localnotification-dismiss-action-tracking
Login Reminder: log notification dismiss event
2 parents 5e43268 + cf09d61 commit ae9c46e

File tree

6 files changed

+114
-10
lines changed

6 files changed

+114
-10
lines changed

WooCommerce/Classes/Analytics/WooAnalyticsStat.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public enum WooAnalyticsStat: String {
3939
case loginJetpackRequiredScreenViewed = "login_jetpack_required_screen_viewed"
4040
case loginJetpackRequiredViewInstructionsButtonTapped = "login_jetpack_required_view_instructions_button_tapped"
4141
case loginLocalNotificationTapped = "login_local_notification_tapped"
42+
case loginLocalNotificationDismissed = "login_local_notification_dismissed"
4243
case loginWhatIsJetpackHelpScreenViewed = "login_what_is_jetpack_help_screen_viewed"
4344
case loginWhatIsJetpackHelpScreenOkButtonTapped = "login_what_is_jetpack_help_screen_ok_button_tapped"
4445
case loginWhatIsJetpackHelpScreenLearnMoreButtonTapped = "login_what_is_jetpack_help_screen_learn_more_button_tapped"

WooCommerce/Classes/Notifications/PushNotificationsManager.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,10 @@ extension PushNotificationsManager {
322322
intentIdentifiers: [],
323323
hiddenPreviewsBodyPlaceholder: nil,
324324
categorySummaryFormat: nil,
325-
options: .allowAnnouncement)
325+
// `customDismissAction` option is required for the dismiss action callback in
326+
// `UNUserNotificationCenterDelegate.userNotificationCenter(_:didReceive:)`
327+
// with action identifier `UNNotificationDismissActionIdentifier`.
328+
options: .customDismissAction)
326329
center.setNotificationCategories([category])
327330
content.categoryIdentifier = categoryIdentifier
328331
}

WooCommerce/Classes/ViewRelated/AppCoordinator.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ final class AppCoordinator {
1616
private let roleEligibilityUseCase: RoleEligibilityUseCaseProtocol
1717
private let analytics: Analytics
1818
private let loggedOutAppSettings: LoggedOutAppSettingsProtocol
19+
private let pushNotesManager: PushNotesManager
1920
private let featureFlagService: FeatureFlagService
2021

2122
private var storePickerCoordinator: StorePickerCoordinator?
@@ -29,6 +30,7 @@ final class AppCoordinator {
2930
roleEligibilityUseCase: RoleEligibilityUseCaseProtocol = RoleEligibilityUseCase(),
3031
analytics: Analytics = ServiceLocator.analytics,
3132
loggedOutAppSettings: LoggedOutAppSettingsProtocol = LoggedOutAppSettings(userDefaults: .standard),
33+
pushNotesManager: PushNotesManager = ServiceLocator.pushNotesManager,
3234
featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) {
3335
self.window = window
3436
self.tabBarController = {
@@ -43,6 +45,7 @@ final class AppCoordinator {
4345
self.roleEligibilityUseCase = roleEligibilityUseCase
4446
self.analytics = analytics
4547
self.loggedOutAppSettings = loggedOutAppSettings
48+
self.pushNotesManager = pushNotesManager
4649
self.featureFlagService = featureFlagService
4750
}
4851

@@ -66,7 +69,7 @@ final class AppCoordinator {
6669
self.isLoggedIn = isLoggedIn
6770
}
6871

69-
localNotificationResponsesSubscription = ServiceLocator.pushNotesManager.localNotificationUserResponses.sink { [weak self] response in
72+
localNotificationResponsesSubscription = pushNotesManager.localNotificationUserResponses.sink { [weak self] response in
7073
self?.handleLocalNotificationResponse(response)
7174
}
7275
}
@@ -250,7 +253,7 @@ private extension AppCoordinator {
250253
"action": "contact_support",
251254
"type": response.notification.request.identifier
252255
])
253-
default:
256+
case UNNotificationDefaultActionIdentifier:
254257
// Triggered when the user taps on the notification itself instead of one of the actions.
255258
switch response.notification.request.identifier {
256259
case LocalNotification.Scenario.loginSiteAddressError.rawValue:
@@ -259,8 +262,20 @@ private extension AppCoordinator {
259262
"type": response.notification.request.identifier
260263
])
261264
default:
262-
return
265+
break
263266
}
267+
case UNNotificationDismissActionIdentifier:
268+
// Triggered when the user taps on the notification's "Clear" action.
269+
switch response.notification.request.identifier {
270+
case LocalNotification.Scenario.loginSiteAddressError.rawValue:
271+
analytics.track(.loginLocalNotificationDismissed, withProperties: [
272+
"type": response.notification.request.identifier
273+
])
274+
default:
275+
break
276+
}
277+
default:
278+
break
264279
}
265280
}
266281
}

WooCommerce/WooCommerceTests/AppCoordinatorTests.swift

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,78 @@ final class AppCoordinatorTests: XCTestCase {
203203
// Then
204204
XCTAssertEqual(analytics.receivedEvents, [WooAnalyticsStat.loginOnboardingShown.rawValue])
205205
}
206+
207+
// MARK: - Login reminder analytics
208+
209+
func test_loginLocalNotificationTapped_is_tracked_after_notification_contact_support_action() throws {
210+
// Given
211+
let analytics = MockAnalyticsProvider()
212+
let pushNotesManager = MockPushNotificationsManager()
213+
let appCoordinator = makeCoordinator(window: window,
214+
stores: stores,
215+
authenticationManager: authenticationManager,
216+
analytics: WooAnalytics(analyticsProvider: analytics),
217+
pushNotesManager: pushNotesManager)
218+
appCoordinator.start()
219+
220+
// When
221+
let response = try XCTUnwrap(MockNotificationResponse(actionIdentifier: LocalNotification.Action.contactSupport.rawValue,
222+
requestIdentifier: LocalNotification.Scenario.loginSiteAddressError.rawValue))
223+
pushNotesManager.sendLocalNotificationResponse(response)
224+
225+
// Then
226+
XCTAssertEqual(analytics.receivedEvents, [WooAnalyticsStat.loginLocalNotificationTapped.rawValue])
227+
let actionPropertyValue = try XCTUnwrap(analytics.receivedProperties.first?["action"] as? String)
228+
XCTAssertEqual(actionPropertyValue, "contact_support")
229+
let typePropertyValue = try XCTUnwrap(analytics.receivedProperties.first?["type"] as? String)
230+
XCTAssertEqual(typePropertyValue, "site_address_error")
231+
}
232+
233+
func test_loginLocalNotificationTapped_is_tracked_after_notification_tap_action() throws {
234+
// Given
235+
let analytics = MockAnalyticsProvider()
236+
let pushNotesManager = MockPushNotificationsManager()
237+
let appCoordinator = makeCoordinator(window: window,
238+
stores: stores,
239+
authenticationManager: authenticationManager,
240+
analytics: WooAnalytics(analyticsProvider: analytics),
241+
pushNotesManager: pushNotesManager)
242+
appCoordinator.start()
243+
244+
// When
245+
let response = try XCTUnwrap(MockNotificationResponse(actionIdentifier: UNNotificationDefaultActionIdentifier,
246+
requestIdentifier: LocalNotification.Scenario.loginSiteAddressError.rawValue))
247+
pushNotesManager.sendLocalNotificationResponse(response)
248+
249+
// Then
250+
XCTAssertEqual(analytics.receivedEvents, [WooAnalyticsStat.loginLocalNotificationTapped.rawValue])
251+
let actionPropertyValue = try XCTUnwrap(analytics.receivedProperties.first?["action"] as? String)
252+
XCTAssertEqual(actionPropertyValue, "default")
253+
let typePropertyValue = try XCTUnwrap(analytics.receivedProperties.first?["type"] as? String)
254+
XCTAssertEqual(typePropertyValue, "site_address_error")
255+
}
256+
257+
func test_loginLocalNotificationDismissed_is_tracked_after_notification_dismiss_action() throws {
258+
// Given
259+
let analytics = MockAnalyticsProvider()
260+
let pushNotesManager = MockPushNotificationsManager()
261+
let appCoordinator = makeCoordinator(window: window,
262+
stores: stores,
263+
authenticationManager: authenticationManager,
264+
analytics: WooAnalytics(analyticsProvider: analytics),
265+
pushNotesManager: pushNotesManager)
266+
appCoordinator.start()
267+
268+
// When
269+
let response = try XCTUnwrap(MockNotificationResponse(actionIdentifier: UNNotificationDismissActionIdentifier,
270+
requestIdentifier: LocalNotification.Scenario.loginSiteAddressError.rawValue))
271+
pushNotesManager.sendLocalNotificationResponse(response)
272+
273+
// Then
274+
XCTAssertEqual(analytics.receivedEvents, [WooAnalyticsStat.loginLocalNotificationDismissed.rawValue])
275+
let typePropertyValue = try XCTUnwrap(analytics.receivedProperties.first?["type"] as? String)
276+
XCTAssertEqual(typePropertyValue, "site_address_error")
277+
}
206278
}
207279

208280
private extension AppCoordinatorTests {
@@ -213,13 +285,15 @@ private extension AppCoordinatorTests {
213285
roleEligibilityUseCase: RoleEligibilityUseCaseProtocol? = nil,
214286
analytics: Analytics = ServiceLocator.analytics,
215287
loggedOutAppSettings: LoggedOutAppSettingsProtocol = MockLoggedOutAppSettings(),
288+
pushNotesManager: PushNotesManager = ServiceLocator.pushNotesManager,
216289
featureFlagService: FeatureFlagService = MockFeatureFlagService()) -> AppCoordinator {
217290
return AppCoordinator(window: window ?? self.window,
218291
stores: stores ?? self.stores,
219292
authenticationManager: authenticationManager ?? self.authenticationManager,
220293
roleEligibilityUseCase: roleEligibilityUseCase ?? MockRoleEligibilityUseCase(),
221294
analytics: analytics,
222295
loggedOutAppSettings: loggedOutAppSettings,
296+
pushNotesManager: pushNotesManager,
223297
featureFlagService: featureFlagService)
224298
}
225299
}

WooCommerce/WooCommerceTests/Mocks/MockPushNotificationsManager.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,11 @@ extension MockPushNotificationsManager {
102102
func sendInactiveNotification(_ notification: PushNotification) {
103103
inactiveNotificationsSubject.send(notification)
104104
}
105+
106+
/// Send a `UNNotificationResponse` that will be emitted by the `localNotificationResponses`
107+
/// observable.
108+
///
109+
func sendLocalNotificationResponse(_ response: UNNotificationResponse) {
110+
localNotificationResponsesSubject.send(response)
111+
}
105112
}

WooCommerce/WooCommerceTests/Mocks/MockUserNotification.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ final class MockUserNotificationCoder: NSCoder {
77
case date, request, // `UNNotification`
88
notification, actionIdentifier // `UNNotificationResponse`
99
}
10-
private let testIdentifier = "testIdentifier"
10+
private let actionIdentifier: String
1111
private let request: UNNotificationRequest
1212

1313
override var allowsKeyedCoding: Bool { true }
1414

15-
init(request: UNNotificationRequest) {
15+
/// - Parameters:
16+
/// - actionIdentifier: for `UNNotificationResponse` only.
17+
/// - request: the request of `UNNotification` or that of a `UNNotificationResponse`.
18+
init(actionIdentifier: String = "", request: UNNotificationRequest) {
19+
self.actionIdentifier = actionIdentifier
1620
self.request = request
1721
}
1822

@@ -26,7 +30,7 @@ final class MockUserNotificationCoder: NSCoder {
2630
return request
2731
// `UNNotificationResponse` fields
2832
case .actionIdentifier:
29-
return testIdentifier
33+
return actionIdentifier
3034
case .notification:
3135
return UNNotification(coder: self)
3236
default:
@@ -69,11 +73,11 @@ final class MockNotification: UNNotification {
6973
}
7074

7175
final class MockNotificationResponse: UNNotificationResponse {
72-
init?(notificationUserInfo: [AnyHashable: Any]) {
73-
let request = UNNotificationRequest.init(identifier: "",
76+
init?(actionIdentifier: String = "", requestIdentifier: String = "", notificationUserInfo: [AnyHashable: Any] = [:]) {
77+
let request = UNNotificationRequest.init(identifier: requestIdentifier,
7478
content: MockNotificationContent(userInfo: notificationUserInfo),
7579
trigger: nil)
76-
super.init(coder: MockUserNotificationCoder(request: request))
80+
super.init(coder: MockUserNotificationCoder(actionIdentifier: actionIdentifier, request: request))
7781
}
7882

7983
required init?(coder: NSCoder) {

0 commit comments

Comments
 (0)