Skip to content

Commit cc8759d

Browse files
authored
[Woo POS][Historical Orders] Order Details: Analytics (#16180)
2 parents b5dd3c7 + 69a5f22 commit cc8759d

File tree

17 files changed

+238
-16
lines changed

17 files changed

+238
-16
lines changed

Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsStat.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,15 @@ public enum WooAnalyticsStat: String {
13051305
case pointOfSaleSettingsHardwareTapped = "settings_hardware_tapped"
13061306
case pointOfSaleSettingsHelpTapped = "settings_help_tapped"
13071307
case pointOfSaleEmptyCartSetupScannerTapped = "empty_cart_set_up_scanner_tapped"
1308+
case pointOfSaleOrdersMenuItemTapped = "orders_menu_item_tapped"
1309+
case pointOfSaleOrdersListPullToRefresh = "orders_list_pull_to_refresh"
1310+
case pointOfSaleOrdersListFetched = "orders_list_fetched"
1311+
case pointOfSaleOrdersListNextPageLoaded = "orders_list_next_page_loaded"
1312+
case pointOfSaleOrdersListRowTapped = "orders_list_row_tapped"
1313+
case pointOfSaleOrdersListSearchButtonTapped = "orders_list_search_button_tapped"
1314+
case pointOfSaleOrdersListSearchResultsFetched = "orders_list_search_results_fetched"
1315+
case pointOfSaleOrderDetailsLoaded = "order_details_loaded"
1316+
case pointOfSaleOrderDetailsEmailReceiptTapped = "order_details_email_receipt_tapped"
13081317

13091318
// MARK: Custom Fields events
13101319
case productDetailCustomFieldsTapped = "product_detail_custom_fields_tapped"

Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrderListFetchStrategy.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import struct NetworkingCore.PagedItems
44
public protocol POSOrderListFetchStrategy {
55
func fetchOrders(pageNumber: Int) async throws -> PagedItems<POSOrder>
66
func loadOrder(orderID: Int64) async throws -> POSOrder
7+
func trackFetched(millisecondsSinceRequestSent: Int)
8+
func trackNextPageLoaded(pageNumber: Int)
79
var supportsCaching: Bool { get }
810
var showsLoadingWithItems: Bool { get }
911
var id: String { get }
@@ -17,11 +19,13 @@ extension POSOrderListFetchStrategy {
1719

1820
struct POSDefaultOrderListFetchStrategy: POSOrderListFetchStrategy {
1921
private let orderListService: POSOrderListServiceProtocol
22+
private let analytics: POSOrderListFetchAnalyticsTracking
2023
let supportsCaching: Bool = true
2124
var showsLoadingWithItems: Bool = true
2225

23-
init(orderListService: POSOrderListServiceProtocol) {
26+
init(orderListService: POSOrderListServiceProtocol, analytics: POSOrderListFetchAnalyticsTracking) {
2427
self.orderListService = orderListService
28+
self.analytics = analytics
2529
}
2630

2731
func fetchOrders(pageNumber: Int) async throws -> PagedItems<POSOrder> {
@@ -31,18 +35,28 @@ struct POSDefaultOrderListFetchStrategy: POSOrderListFetchStrategy {
3135
func loadOrder(orderID: Int64) async throws -> POSOrder {
3236
try await orderListService.loadOrder(orderID: orderID)
3337
}
38+
39+
func trackFetched(millisecondsSinceRequestSent: Int) {
40+
analytics.trackOrdersFetchComplete(millisecondsSinceRequestSent: millisecondsSinceRequestSent)
41+
}
42+
43+
func trackNextPageLoaded(pageNumber: Int) {
44+
analytics.trackOrdersNextPageLoaded(pageNumber: pageNumber)
45+
}
3446
}
3547

3648
struct POSSearchOrderListFetchStrategy: POSOrderListFetchStrategy {
3749
private let orderListService: POSOrderListServiceProtocol
3850
private let searchTerm: String
51+
private let analytics: POSOrderListFetchAnalyticsTracking
3952

4053
var supportsCaching: Bool = false
4154
var showsLoadingWithItems = false
4255

43-
init(orderListService: POSOrderListServiceProtocol, searchTerm: String) {
56+
init(orderListService: POSOrderListServiceProtocol, searchTerm: String, analytics: POSOrderListFetchAnalyticsTracking) {
4457
self.orderListService = orderListService
4558
self.searchTerm = searchTerm
59+
self.analytics = analytics
4660
}
4761

4862
func fetchOrders(pageNumber: Int) async throws -> PagedItems<POSOrder> {
@@ -52,4 +66,12 @@ struct POSSearchOrderListFetchStrategy: POSOrderListFetchStrategy {
5266
func loadOrder(orderID: Int64) async throws -> POSOrder {
5367
try await orderListService.loadOrder(orderID: orderID)
5468
}
69+
70+
func trackFetched(millisecondsSinceRequestSent: Int) {
71+
analytics.trackOrdersSearchResultsFetchComplete(millisecondsSinceRequestSent: millisecondsSinceRequestSent)
72+
}
73+
74+
func trackNextPageLoaded(pageNumber: Int) {
75+
analytics.trackOrdersNextPageLoaded(pageNumber: pageNumber)
76+
}
5577
}

Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrderListFetchStrategyFactory.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@ public final class POSOrderListFetchStrategyFactory: POSOrderListFetchStrategyFa
1414
private let siteID: Int64
1515
private let ordersRemote: OrdersRemote
1616
private let currencyFormatter: CurrencyFormatter
17+
private let analytics: POSOrderListFetchAnalyticsTracking
1718

1819
public init(siteID: Int64,
1920
credentials: Credentials?,
2021
selectedSite: AnyPublisher<JetpackSite?, Never>,
2122
appPasswordSupportState: AnyPublisher<Bool, Never>,
22-
currencyFormatter: CurrencyFormatter) {
23+
currencyFormatter: CurrencyFormatter,
24+
analytics: POSOrderListFetchAnalyticsTracking) {
2325
self.siteID = siteID
2426
let network = AlamofireNetwork(credentials: credentials,
2527
selectedSite: selectedSite,
2628
appPasswordSupportState: appPasswordSupportState)
2729
self.ordersRemote = OrdersRemote(network: network)
2830
self.currencyFormatter = currencyFormatter
31+
self.analytics = analytics
2932
}
3033

3134
public func defaultStrategy() -> POSOrderListFetchStrategy {
@@ -34,7 +37,8 @@ public final class POSOrderListFetchStrategyFactory: POSOrderListFetchStrategyFa
3437
siteID: siteID,
3538
ordersRemote: ordersRemote,
3639
currencyFormatter: currencyFormatter
37-
)
40+
),
41+
analytics: analytics
3842
)
3943
}
4044

@@ -45,7 +49,8 @@ public final class POSOrderListFetchStrategyFactory: POSOrderListFetchStrategyFa
4549
ordersRemote: ordersRemote,
4650
currencyFormatter: currencyFormatter
4751
),
48-
searchTerm: searchTerm
52+
searchTerm: searchTerm,
53+
analytics: analytics
4954
)
5055
}
5156
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Foundation
2+
3+
/// Protocol defining analytics tracking for Point of Sale orders fetch functionality
4+
public protocol POSOrderListFetchAnalyticsTracking {
5+
/// Tracks when a remote orders fetch completes
6+
/// - Parameters:
7+
/// - millisecondsSinceRequestSent: The time taken to fetch results in milliseconds
8+
func trackOrdersFetchComplete(millisecondsSinceRequestSent: Int)
9+
10+
/// Tracks when a remote search results fetch completes for orders
11+
/// - Parameters:
12+
/// - millisecondsSinceRequestSent: The time taken to fetch results in milliseconds
13+
func trackOrdersSearchResultsFetchComplete(millisecondsSinceRequestSent: Int)
14+
15+
/// Tracks when next page of orders is loaded
16+
/// - Parameters:
17+
/// - pageNumber: The page number that was loaded
18+
func trackOrdersNextPageLoaded(pageNumber: Int)
19+
}

WooCommerce/Classes/Analytics/TracksProvider.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,18 @@ private extension TracksProvider {
165165
WooAnalyticsStat.pointOfSaleBarcodeScannerSetupDismissed,
166166
WooAnalyticsStat.pointOfSaleBarcodeScannerSetupRetryTapped,
167167
WooAnalyticsStat.pointOfSaleBarcodeScannerSetupScannerConnected,
168+
WooAnalyticsStat.pointOfSaleOrdersMenuItemTapped,
169+
WooAnalyticsStat.pointOfSaleOrdersListPullToRefresh,
170+
WooAnalyticsStat.pointOfSaleOrdersListFetched,
171+
WooAnalyticsStat.pointOfSaleOrdersListNextPageLoaded,
172+
WooAnalyticsStat.pointOfSaleOrdersListRowTapped,
173+
WooAnalyticsStat.pointOfSaleOrdersListSearchButtonTapped,
174+
WooAnalyticsStat.pointOfSaleOrdersListSearchResultsFetched,
175+
WooAnalyticsStat.pointOfSaleOrderDetailsLoaded,
176+
WooAnalyticsStat.pointOfSaleOrderDetailsEmailReceiptTapped,
168177

169178
// Order
179+
WooAnalyticsStat.ordersListLoaded,
170180
WooAnalyticsStat.orderCreationSuccess,
171181
WooAnalyticsStat.orderCreationFailed,
172182

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
import Yosemite
3+
4+
struct POSOrderListFetchAnalytics: POSOrderListFetchAnalyticsTracking {
5+
private let analytics: POSAnalyticsProviding
6+
7+
init(analytics: POSAnalyticsProviding) {
8+
self.analytics = analytics
9+
}
10+
11+
func trackOrdersFetchComplete(millisecondsSinceRequestSent: Int) {
12+
analytics.track(event: WooAnalyticsEvent.PointOfSale.ordersListFetched(
13+
millisecondsSinceRequestSent: millisecondsSinceRequestSent
14+
))
15+
}
16+
17+
func trackOrdersSearchResultsFetchComplete(millisecondsSinceRequestSent: Int) {
18+
analytics.track(event: WooAnalyticsEvent.PointOfSale.ordersListSearchResultsFetched(
19+
millisecondsSinceRequestSent: millisecondsSinceRequestSent
20+
))
21+
}
22+
23+
func trackOrdersNextPageLoaded(pageNumber: Int) {
24+
analytics.track(event: WooAnalyticsEvent.PointOfSale.ordersListNextPageLoaded(
25+
pageNumber: pageNumber
26+
))
27+
}
28+
}

WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSale.swift

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Foundation
12
import enum Yosemite.CardPresentPaymentOnboardingState
23
import enum Yosemite.POSItemType
34
import enum Yosemite.POSItem
@@ -37,6 +38,11 @@ extension WooAnalyticsEvent {
3738
static let scanner = "scanner"
3839
static let step = "step"
3940
static let scanValue = "scan_value"
41+
static let orderID = "order_id"
42+
static let orderStatus = "order_status"
43+
static let listPosition = "list_position"
44+
static let daysSinceCreated = "days_since_created"
45+
static let pageNumber = "page_number"
4046
}
4147

4248
/// Source of the event where the event is triggered
@@ -376,6 +382,69 @@ extension WooAnalyticsEvent {
376382
WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupScannerConnected,
377383
properties: [Key.scanner: scanner.analyticsName])
378384
}
385+
386+
// MARK: - Orders Analytics Events
387+
388+
static func ordersMenuItemTapped() -> WooAnalyticsEvent {
389+
WooAnalyticsEvent(statName: .pointOfSaleOrdersMenuItemTapped, properties: [:])
390+
}
391+
392+
static func ordersListPullToRefresh() -> WooAnalyticsEvent {
393+
WooAnalyticsEvent(statName: .pointOfSaleOrdersListPullToRefresh, properties: [:])
394+
}
395+
396+
static func ordersListFetched(millisecondsSinceRequestSent: Int) -> WooAnalyticsEvent {
397+
WooAnalyticsEvent(statName: .pointOfSaleOrdersListFetched,
398+
properties: [Key.millisecondsSinceRequestSent: "\(millisecondsSinceRequestSent)"])
399+
}
400+
401+
static func ordersListNextPageLoaded(pageNumber: Int) -> WooAnalyticsEvent {
402+
WooAnalyticsEvent(statName: .pointOfSaleOrdersListNextPageLoaded,
403+
properties: [Key.pageNumber: "\(pageNumber)"])
404+
}
405+
406+
static func ordersListRowTapped(orderID: Int64,
407+
orderStatus: String,
408+
listPosition: Int,
409+
orderCreatedDate: Date,
410+
siteTimezone: TimeZone) -> WooAnalyticsEvent {
411+
WooAnalyticsEvent(statName: .pointOfSaleOrdersListRowTapped,
412+
properties: [
413+
Key.orderID: "\(orderID)",
414+
Key.orderStatus: orderStatus,
415+
Key.listPosition: "\(listPosition)",
416+
Key.daysSinceCreated: "\(daysSinceCreated(from: orderCreatedDate, using: siteTimezone))"
417+
])
418+
}
419+
420+
static func ordersListSearchButtonTapped() -> WooAnalyticsEvent {
421+
WooAnalyticsEvent(statName: .pointOfSaleOrdersListSearchButtonTapped, properties: [:])
422+
}
423+
424+
static func ordersListSearchResultsFetched(millisecondsSinceRequestSent: Int) -> WooAnalyticsEvent {
425+
WooAnalyticsEvent(statName: .pointOfSaleOrdersListSearchResultsFetched,
426+
properties: [Key.millisecondsSinceRequestSent: "\(millisecondsSinceRequestSent)"])
427+
}
428+
429+
static func orderDetailsLoaded(orderID: Int64,
430+
orderStatus: String,
431+
orderCreatedDate: Date,
432+
siteTimezone: TimeZone) -> WooAnalyticsEvent {
433+
WooAnalyticsEvent(statName: .pointOfSaleOrderDetailsLoaded,
434+
properties: [
435+
Key.orderID: "\(orderID)",
436+
Key.orderStatus: orderStatus,
437+
Key.daysSinceCreated: "\(daysSinceCreated(from: orderCreatedDate, using: siteTimezone))"
438+
])
439+
}
440+
441+
static func orderDetailsEmailReceiptTapped() -> WooAnalyticsEvent {
442+
WooAnalyticsEvent(statName: .pointOfSaleOrderDetailsEmailReceiptTapped, properties: [:])
443+
}
444+
445+
static func ordersListLoaded() -> WooAnalyticsEvent {
446+
WooAnalyticsEvent(statName: .ordersListLoaded, properties: [:])
447+
}
379448
}
380449
}
381450

@@ -387,4 +456,10 @@ private extension WooAnalyticsEvent.PointOfSale {
387456
static func safeGatewayID(for gatewayID: String?) -> String {
388457
gatewayID ?? "unknown"
389458
}
459+
460+
static func daysSinceCreated(from date: Date, using siteTimezone: TimeZone) -> Int {
461+
var calendar = Calendar.current
462+
calendar.timeZone = siteTimezone
463+
return calendar.dateComponents([.day], from: date, to: Date()).day ?? 0
464+
}
390465
}

WooCommerce/Classes/POS/Controllers/POSOrderListController.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ protocol POSSearchingOrderListControllerProtocol: POSOrderListControllerProtocol
6565
return
6666
}
6767
let currentOrders = ordersViewState.orders
68-
ordersViewState = fetchStrategy.showsLoadingWithItems ? .loading(currentOrders) : .loading([])
68+
ordersViewState = .loading(currentOrders)
6969
do {
7070
_ = try await paginationTracker.ensureNextPageIsSynced { [weak self] pageNumber in
7171
guard let self else { return true }
@@ -112,8 +112,11 @@ protocol POSSearchingOrderListControllerProtocol: POSOrderListControllerProtocol
112112

113113
@MainActor
114114
private func fetchOrders(pageNumber: Int, appendToExistingOrders: Bool = true) async throws -> Bool {
115+
let startTime = Date()
115116
do {
116117
let pagedOrders = try await fetchStrategy.fetchOrders(pageNumber: pageNumber)
118+
let endTime = Date()
119+
let millisecondsSinceRequestSent = Int(endTime.timeIntervalSince(startTime) * 1000)
117120

118121
let existingOrders = appendToExistingOrders ? ordersViewState.orders : []
119122
let uniqueNewOrders = pagedOrders.items.filter { newOrder in
@@ -132,6 +135,12 @@ protocol POSSearchingOrderListControllerProtocol: POSOrderListControllerProtocol
132135
cachedOrders = allOrders
133136
}
134137

138+
if pageNumber > 1 {
139+
fetchStrategy.trackNextPageLoaded(pageNumber: pageNumber)
140+
} else {
141+
fetchStrategy.trackFetched(millisecondsSinceRequestSent: millisecondsSinceRequestSent)
142+
}
143+
135144
return pagedOrders.hasMorePages
136145
} catch POSOrderListServiceError.requestCancelled {
137146
return true

WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift renamed to WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct POSOrderDetailsView: View {
1212
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
1313
@Environment(\.siteTimezone) private var siteTimezone
1414
@Environment(POSOrderListModel.self) private var orderListModel
15+
@Environment(\.posAnalytics) private var analytics
1516
@State private var isShowingEmailReceiptView: Bool = false
1617

1718
private var shouldShowBackButton: Bool {
@@ -55,6 +56,14 @@ struct POSOrderDetailsView: View {
5556
}
5657
.posHeaderBackButtonIcon(systemName: "xmark")
5758
}
59+
.onAppear {
60+
analytics.track(event: WooAnalyticsEvent.PointOfSale.orderDetailsLoaded(
61+
orderID: order.id,
62+
orderStatus: order.status.rawValue,
63+
orderCreatedDate: order.dateCreated,
64+
siteTimezone: siteTimezone
65+
))
66+
}
5867
}
5968
}
6069

@@ -331,6 +340,7 @@ private extension POSOrderDetailsView {
331340
Button(action: {
332341
switch action {
333342
case .emailReceipt:
343+
analytics.track(event: WooAnalyticsEvent.PointOfSale.orderDetailsEmailReceiptTapped())
334344
isShowingEmailReceiptView = true
335345
}
336346
}) {

WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ struct POSOrderListView: View {
77

88
@Environment(POSOrderListModel.self) private var orderListModel
99
@Environment(\.keyboardObserver) private var keyboardObserver
10+
@Environment(\.posAnalytics) private var analytics
11+
@Environment(\.siteTimezone) private var siteTimezone
1012
@StateObject private var infiniteScrollTriggerDeterminer = ThresholdInfiniteScrollTriggerDeterminer()
1113

1214
@State private var isSearching: Bool = false
@@ -39,6 +41,7 @@ struct POSOrderListView: View {
3941
backgroundColor: .posSurface,
4042
imageColor: .posOnSurface
4143
) {
44+
analytics.track(event: WooAnalyticsEvent.PointOfSale.ordersListSearchButtonTapped())
4245
setSearch(true)
4346
}
4447
.matchedGeometryEffect(id: Constants.searchControlID, in: searchTransition)
@@ -90,6 +93,7 @@ struct POSOrderListView: View {
9093
.background(Color.posSurfaceBright)
9194
.navigationBarHidden(true)
9295
.refreshable {
96+
analytics.track(event: WooAnalyticsEvent.PointOfSale.ordersListPullToRefresh())
9397
await orderListModel.ordersController.refreshOrders()
9498
}
9599
.task {
@@ -124,8 +128,15 @@ struct POSOrderListView: View {
124128
headerRows
125129

126130
let orders = ordersViewState.orders
127-
ForEach(orders, id: \.id) { order in
131+
ForEach(Array(orders.enumerated()), id: \.element.id) { index, order in
128132
Button(action: {
133+
analytics.track(event: WooAnalyticsEvent.PointOfSale.ordersListRowTapped(
134+
orderID: order.id,
135+
orderStatus: order.status.rawValue,
136+
listPosition: index,
137+
orderCreatedDate: order.dateCreated,
138+
siteTimezone: siteTimezone
139+
))
129140
orderListModel.ordersController.selectOrder(order)
130141
}) {
131142
OrderRowView(order: order, isSelected: orderListModel.ordersController.selectedOrder?.id == order.id)

0 commit comments

Comments
 (0)