Skip to content

Commit c87678f

Browse files
committed
Add FXIOS-12039 [Homepage] section viewed telemetry
1 parent 04eb760 commit c87678f

File tree

14 files changed

+138
-131
lines changed

14 files changed

+138
-131
lines changed

firefox-ios/Client.xcodeproj/project.pbxproj

+4-4
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,8 @@
155155
1D3822E92BAB99250046BC5E /* UIView+ThemeUUIDIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3822E82BAB99250046BC5E /* UIView+ThemeUUIDIdentifiable.swift */; };
156156
1D3C90882ACE1AF400304C87 /* RemoteTabPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3C90872ACE1AF400304C87 /* RemoteTabPanelTests.swift */; };
157157
1D4D79462BF2F4E7007C6796 /* SimpleTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E69EAF254D064E00B591C2 /* SimpleTab.swift */; };
158-
1D4D79472BF2F4FD007C6796 /* Throttler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D95015270238500079D39D /* Throttler.swift */; };
159158
1D558A582BED7ECB001EF527 /* MockWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D558A562BED7ECB001EF527 /* MockWindowManager.swift */; };
160159
1D558A5A2BEE7D07001EF527 /* WindowSimpleTabsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D558A592BEE7D07001EF527 /* WindowSimpleTabsCoordinator.swift */; };
161-
1D558A5B2BEE7D07001EF527 /* WindowSimpleTabsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D558A592BEE7D07001EF527 /* WindowSimpleTabsCoordinator.swift */; };
162160
1D5CBF492B17E3CB0001D033 /* NotificationPayloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D5CBF482B17E3CB0001D033 /* NotificationPayloads.swift */; };
163161
1D5CBF4A2B17E3CB0001D033 /* NotificationPayloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D5CBF482B17E3CB0001D033 /* NotificationPayloads.swift */; };
164162
1D69FF8D27B17286001F660E /* HomeLogoHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D69FF8C27B17285001F660E /* HomeLogoHeaderCell.swift */; };
@@ -859,6 +857,7 @@
859857
8A4190D22A6B0848001E8401 /* StatusBarOverlayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4190D02A6B0843001E8401 /* StatusBarOverlayTests.swift */; };
860858
8A4490922BF3BC2700E7E682 /* MicrosurveyPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4490912BF3BC2700E7E682 /* MicrosurveyPromptView.swift */; };
861859
8A4490952BF3C42B00E7E682 /* MicrosurveyConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4490942BF3C42B00E7E682 /* MicrosurveyConfirmationView.swift */; };
860+
8A44D3612DB7DFD700B7D80B /* MockThrottler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A44D3602DB7DFD200B7D80B /* MockThrottler.swift */; };
862861
8A44F20E2B585E1F0016BC81 /* HomepageTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A44F20D2B585E1F0016BC81 /* HomepageTelemetry.swift */; };
863862
8A454D292CB7078D009436D9 /* PocketState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A454D282CB7078D009436D9 /* PocketState.swift */; };
864863
8A454D2C2CB81153009436D9 /* PocketStandardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A454D2B2CB81153009436D9 /* PocketStandardCell.swift */; };
@@ -8101,6 +8100,7 @@
81018100
8A4190D02A6B0843001E8401 /* StatusBarOverlayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarOverlayTests.swift; sourceTree = "<group>"; };
81028101
8A4490912BF3BC2700E7E682 /* MicrosurveyPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MicrosurveyPromptView.swift; sourceTree = "<group>"; };
81038102
8A4490942BF3C42B00E7E682 /* MicrosurveyConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyConfirmationView.swift; sourceTree = "<group>"; };
8103+
8A44D3602DB7DFD200B7D80B /* MockThrottler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockThrottler.swift; sourceTree = "<group>"; };
81048104
8A44F20D2B585E1F0016BC81 /* HomepageTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageTelemetry.swift; sourceTree = "<group>"; };
81058105
8A454D282CB7078D009436D9 /* PocketState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PocketState.swift; sourceTree = "<group>"; };
81068106
8A454D2B2CB81153009436D9 /* PocketStandardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PocketStandardCell.swift; sourceTree = "<group>"; };
@@ -12488,6 +12488,7 @@
1248812488
8A87B4352CC1A8FD003A9239 /* Mock */ = {
1248912489
isa = PBXGroup;
1249012490
children = (
12491+
8A44D3602DB7DFD200B7D80B /* MockThrottler.swift */,
1249112492
8A03FFA22DB12AF700509A72 /* MockSponsoredTelemetry.swift */,
1249212493
8AEF41632D15EE190013925D /* MockTopSitesManager.swift */,
1249312494
8A87B4362CC1A910003A9239 /* MockPocketManager.swift */,
@@ -17980,6 +17981,7 @@
1798017981
8A8482F02BE1602500F9007B /* MicrosurveyPromptStateTests.swift in Sources */,
1798117982
8ADED7EC27691351009C19E6 /* CalendarExtensionsTests.swift in Sources */,
1798217983
3B39EDBA1E16E18900EF029F /* CustomSearchEnginesTest.swift in Sources */,
17984+
8A44D3612DB7DFD700B7D80B /* MockThrottler.swift in Sources */,
1798317985
8A7892072CF9228700490CA4 /* UnifiedAdsCallbackTelemetryTests.swift in Sources */,
1798417986
0AFF7F662C7784F100265214 /* TrackingProtectionModelTests.swift in Sources */,
1798517987
C80C11EE28B3C8B80062922A /* WallpaperMetadataTrackerTests.swift in Sources */,
@@ -17998,7 +18000,6 @@
1799818000
ED07C0F52CCB020B006C0627 /* SearchEngineSelectionMiddlewareTests.swift in Sources */,
1799918001
C8DF92F72A14101500AA7B05 /* OnboardingViewControllerProtocolTests.swift in Sources */,
1800018002
8A4EA0D92C01127C00E4E4F1 /* MicrosurveyMockModel.swift in Sources */,
18001-
1D4D79472BF2F4FD007C6796 /* Throttler.swift in Sources */,
1800218003
6A3E5D8A283831D1001E706E /* DownloadQueueTests.swift in Sources */,
1800318004
8AEF41642D15EE1D0013925D /* MockTopSitesManager.swift in Sources */,
1800418005
8AE80BB62891AEA100BC12EA /* MockDispatchGroup.swift in Sources */,
@@ -18175,7 +18176,6 @@
1817518176
8A7835372D5107810052E328 /* BookmarksMiddlewareTests.swift in Sources */,
1817618177
45D5EDC0292D619000311934 /* MockablePinnedSites.swift in Sources */,
1817718178
5AE371852A4DD6FE0092A760 /* PasswordManagerCoordinatorDelegateMock.swift in Sources */,
18178-
1D558A5B2BEE7D07001EF527 /* WindowSimpleTabsCoordinator.swift in Sources */,
1817918179
8A6E8DEB2B275BA9000C4301 /* PrivateHomepageViewControllerTests.swift in Sources */,
1818018180
8A93F86529D37331004159D9 /* DefaultRouterTests.swift in Sources */,
1818118181
5AD3B6802CF6674F00AFA1FE /* MockUIApplication.swift in Sources */,

firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift

+60-47
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ final class HomepageViewController: UIViewController,
6363
private let overlayManager: OverlayModeManager
6464
private let logger: Logger
6565
private let toastContainer: UIView
66-
private var alreadyTrackedItems = Set<HomepageItem>()
66+
67+
// Telemetry related
68+
private var alreadyTrackedSections = Set<HomepageSection>()
69+
private var alreadyTrackedTopSites = Set<HomepageItem>()
70+
private let trackingImpressionsThrottler: ThrottleProtocol
6771

6872
// MARK: - Initializers
6973
init(windowUUID: WindowUUID,
@@ -73,7 +77,8 @@ final class HomepageViewController: UIViewController,
7377
statusBarScrollDelegate: StatusBarScrollDelegate? = nil,
7478
toastContainer: UIView,
7579
notificationCenter: NotificationProtocol = NotificationCenter.default,
76-
logger: Logger = DefaultLogger.shared
80+
logger: Logger = DefaultLogger.shared,
81+
throttler: ThrottleProtocol = Throttler(seconds: 0.5)
7782
) {
7883
self.windowUUID = windowUUID
7984
self.themeManager = themeManager
@@ -82,6 +87,7 @@ final class HomepageViewController: UIViewController,
8287
self.statusBarScrollDelegate = statusBarScrollDelegate
8388
self.toastContainer = toastContainer
8489
self.logger = logger
90+
self.trackingImpressionsThrottler = throttler
8591

8692
// FXIOS-11490: This should be refactored when we refactor CFR to adhere to Redux
8793
let jumpBackInContextualViewProvider = ContextualHintViewProvider(
@@ -179,7 +185,7 @@ final class HomepageViewController: UIViewController,
179185

180186
override func viewDidDisappear(_ animated: Bool) {
181187
super.viewDidDisappear(animated)
182-
alreadyTrackedItems.removeAll()
188+
resetTrackedObjects()
183189
}
184190

185191
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
@@ -297,9 +303,9 @@ final class HomepageViewController: UIViewController,
297303
)
298304
// FXIOS-11523 - Trigger impression when user opens homepage view new tab + scroll to top
299305
if homepageState.shouldTriggerImpression {
300-
alreadyTrackedItems.removeAll()
301-
trackVisibleItemImpressions()
302306
scrollToTop()
307+
resetTrackedObjects()
308+
trackVisibleItemImpressions()
303309
}
304310
}
305311

@@ -769,7 +775,7 @@ final class HomepageViewController: UIViewController,
769775
)
770776
}
771777

772-
private func dispatchOpenTopSitesAction(at index: Int, config: TopSiteConfiguration) {
778+
private func dispatchTopSitesAction(at index: Int, config: TopSiteConfiguration, actionType: ActionType) {
773779
let config = TopSitesTelemetryConfig(
774780
isZeroSearch: homepageState.isZeroSearch,
775781
position: index,
@@ -779,7 +785,7 @@ final class HomepageViewController: UIViewController,
779785
TopSitesAction(
780786
telemetryConfig: config,
781787
windowUUID: self.windowUUID,
782-
actionType: TopSitesActionType.tapOnHomepageTopSitesCell
788+
actionType: actionType
783789
)
784790
)
785791
}
@@ -804,7 +810,11 @@ final class HomepageViewController: UIViewController,
804810
visitType: .link
805811
)
806812
dispatchNavigationBrowserAction(with: destination, actionType: NavigationBrowserActionType.tapOnCell)
807-
dispatchOpenTopSitesAction(at: indexPath.item, config: config)
813+
dispatchTopSitesAction(
814+
at: indexPath.item,
815+
config: config,
816+
actionType: TopSitesActionType.tapOnHomepageTopSitesCell
817+
)
808818
case .jumpBackIn(let config):
809819
store.dispatch(
810820
JumpBackInAction(
@@ -867,50 +877,53 @@ final class HomepageViewController: UIViewController,
867877
)
868878
}
869879

870-
/// Want to handle tracking here to capture any cells about to viewed,
871-
/// but some cells do not get reconfigured so we add additional tracking detection with `trackVisibleImpressions`
872-
func collectionView(
873-
_ collectionView: UICollectionView,
874-
willDisplay cell: UICollectionViewCell,
875-
forItemAt indexPath: IndexPath
876-
) {
877-
guard let item = dataSource?.itemIdentifier(for: indexPath) else { return }
878-
handleTrackingItemImpression(with: item, at: indexPath.item)
879-
}
880-
881-
/// Used to track item impressions. If the user has seen the item on the homepage, we only record the impression once.
880+
/// Used to track impressions. If the user has already seen the item on the homepage, we only record the impression once.
882881
/// We want to track at initial seen as well as when users scrolls.
882+
/// A throttle is added in order to capture what the users has seen. When we scroll to top programmatically,
883+
/// the impressions were being tracked, but to match user's perspective, we add a throttle to delay.
884+
/// Time complexity: O(n) due to iterating visible items.
883885
private func trackVisibleItemImpressions() {
884-
guard let collectionView else {
885-
logger.log(
886-
"Homepage collectionview should not have been nil, unable to track impression",
887-
level: .warning,
888-
category: .homepage
889-
)
890-
return
891-
}
892-
for indexPath in collectionView.indexPathsForVisibleItems {
893-
guard let item = dataSource?.itemIdentifier(for: indexPath) else { continue }
894-
handleTrackingItemImpression(with: item, at: indexPath.item)
886+
trackingImpressionsThrottler.throttle { [weak self] in
887+
guard let self else { return }
888+
guard let collectionView else {
889+
logger.log(
890+
"Homepage collectionview should not have been nil, unable to track impression",
891+
level: .warning,
892+
category: .homepage
893+
)
894+
return
895+
}
896+
for indexPath in collectionView.indexPathsForVisibleItems {
897+
guard let section = dataSource?.sectionIdentifier(for: indexPath.section),
898+
let item = dataSource?.itemIdentifier(for: indexPath) else { continue }
899+
handleTrackingImpressions(for: section, with: item, at: indexPath.item)
900+
}
895901
}
896902
}
897903

898-
private func handleTrackingItemImpression(with item: HomepageItem, at index: Int) {
899-
guard !alreadyTrackedItems.contains(item) else { return }
900-
alreadyTrackedItems.insert(item)
901-
if case .topSite(let config, _) = item {
902-
sendItemActionWithTelemetryExtras(
903-
item: item,
904-
actionType: HomepageActionType.itemSeen,
905-
topSitesTelemetryConfig: TopSitesTelemetryConfig(
906-
isZeroSearch: homepageState.isZeroSearch,
907-
position: index,
908-
topSiteConfiguration: config
909-
)
910-
)
911-
} else {
912-
sendItemActionWithTelemetryExtras(item: item, actionType: HomepageActionType.itemSeen)
913-
}
904+
/// We want to capture generic section impressions,
905+
/// but we also need to handle capturing individual sponsored tiles impressions
906+
private func handleTrackingImpressions(for section: HomepageSection, with item: HomepageItem, at index: Int) {
907+
handleTrackingTopSitesImpression(for: item, at: index)
908+
handleTrackingSectionImpression(for: section, with: item)
909+
}
910+
911+
private func handleTrackingTopSitesImpression(for item: HomepageItem, at index: Int) {
912+
guard !alreadyTrackedTopSites.contains(item) else { return }
913+
alreadyTrackedTopSites.insert(item)
914+
guard case .topSite(let config, _) = item else { return }
915+
dispatchTopSitesAction(at: index, config: config, actionType: TopSitesActionType.topSitesSeen)
916+
}
917+
918+
private func handleTrackingSectionImpression(for section: HomepageSection, with item: HomepageItem) {
919+
guard !alreadyTrackedSections.contains(section) else { return }
920+
alreadyTrackedSections.insert(section)
921+
sendItemActionWithTelemetryExtras(item: item, actionType: HomepageActionType.sectionSeen)
922+
}
923+
924+
private func resetTrackedObjects() {
925+
alreadyTrackedSections.removeAll()
926+
alreadyTrackedTopSites.removeAll()
914927
}
915928

916929
// MARK: - UIPopoverPresentationControllerDelegate - Context Hints (CFR)

firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageAction.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ enum HomepageActionType: ActionType {
4242
case viewWillAppear
4343
case didSelectItem
4444
case embeddedHomepage
45-
case itemSeen
45+
case sectionSeen
4646
}

firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageMiddleware.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ final class HomepageMiddleware {
3838
}
3939
self.homepageTelemetry.sendItemTappedTelemetryEvent(for: type)
4040

41-
case HomepageActionType.itemSeen:
41+
case HomepageActionType.sectionSeen:
4242
guard let extras = (action as? HomepageAction)?.telemetryExtras, let type = extras.itemType else {
4343
return
4444
}
45-
self.homepageTelemetry.sendItemImpressionTelemetryEvent(for: type)
45+
self.homepageTelemetry.sendSectionLabeledCounter(for: type)
4646

4747
default:
4848
break

firefox-ios/Client/Frontend/Home/Homepage Rebuild/TopSites/TopSitesAction.swift

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ enum TopSitesActionType: ActionType {
3939
case toggleShowSectionSetting
4040
case toggleShowSponsoredSettings
4141
case tapOnHomepageTopSitesCell
42+
case topSitesSeen
4243
}
4344

4445
enum TopSitesMiddlewareActionType: ActionType {

firefox-ios/Client/Frontend/Home/Homepage Rebuild/TopSites/TopSitesMiddleware.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ final class TopSitesMiddleware: FeatureFlaggable {
5252
case HomepageActionType.initialize:
5353
self.getTopSitesDataAndUpdateState(for: action)
5454

55-
case HomepageActionType.itemSeen:
55+
case TopSitesActionType.topSitesSeen:
5656
self.handleSponsoredImpressionTracking(for: action)
5757

5858
case TopSitesActionType.fetchTopSites:
@@ -154,7 +154,7 @@ final class TopSitesMiddleware: FeatureFlaggable {
154154

155155
// MARK: Telemetry
156156
private func handleSponsoredImpressionTracking(for action: Action) {
157-
guard let telemetryMetadata = (action as? HomepageAction)?.telemetryExtras?.topSitesTelemetryConfig else {
157+
guard let telemetryMetadata = (action as? TopSitesAction)?.telemetryConfig else {
158158
self.logger.log(
159159
"Unable to retrieve telemetryMetadata for \(action.actionType)",
160160
level: .warning,

firefox-ios/Client/Frontend/Home/TopSites/DataManagement/UnifiedAds/UnifiedAdsProvider.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,9 @@ class UnifiedAdsProvider: URLCaching, UnifiedAdsProviderInterface, FeatureFlagga
156156
}
157157

158158
private var resourceEndpoint: URL? {
159-
if featureFlags.isCoreFeatureEnabled(.useStagingContileAPI) {
160-
return URL(string: UnifiedAdsProvider.stagingResourceEndpoint)
161-
}
159+
// if featureFlags.isCoreFeatureEnabled(.useStagingContileAPI) {
160+
// return URL(string: UnifiedAdsProvider.stagingResourceEndpoint)
161+
// }
162162
return URL(string: UnifiedAdsProvider.prodResourceEndpoint)
163163
}
164164
}

firefox-ios/Client/Telemetry/HomepageTelemetry.swift

+2-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,8 @@ struct HomepageTelemetry {
4949
gleanWrapper.recordEvent(for: GleanMetrics.Homepage.itemTapped, extras: itemNameExtra)
5050
}
5151

52-
func sendItemImpressionTelemetryEvent(for itemType: ItemType) {
53-
let itemNameExtra = GleanMetrics.Homepage.ItemViewedExtra(section: itemType.sectionName, type: itemType.rawValue)
54-
gleanWrapper.recordEvent(for: GleanMetrics.Homepage.itemViewed, extras: itemNameExtra)
52+
func sendSectionLabeledCounter(for itemType: ItemType) {
53+
gleanWrapper.recordLabel(for: GleanMetrics.Homepage.sectionViewed, label: itemType.sectionName)
5554
}
5655

5756
// MARK: - Top Sites

firefox-ios/Client/Utils/Throttler.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
import Foundation
66
import Common
77

8+
protocol ThrottleProtocol {
9+
func throttle(completion: @escaping () -> Void)
10+
}
11+
812
/// For any work that needs to be delayed, you can wrap it inside a throttler
913
/// and specify the delay time, in seconds, and queue.
10-
class Throttler {
14+
class Throttler: ThrottleProtocol {
1115
private let defaultDelay = 0.35
1216

1317
private let threshold: Double

firefox-ios/Client/metrics.yaml

+10-13
Original file line numberDiff line numberDiff line change
@@ -2349,23 +2349,20 @@ homepage:
23492349
tags:
23502350
- Homepage
23512351

2352-
item_viewed:
2353-
type: event
2352+
section_viewed:
2353+
type: labeled_counter
23542354
description: |
23552355
Records when an item has been viewed on the homepage. See `homepage.viewed` for more details on what is considered a homepage view. This event refers to an item has been scrolled to or seen on an homepage that has been viewed.
2356-
extra_keys:
2357-
section:
2358-
type: string
2359-
description: |
2360-
The section that the item belongs to on the homepage. This section name is found in `HomepageTelemetry.ItemType` under `sectionName`.
2361-
type:
2362-
type: string
2363-
description: |
2364-
The type of item that was tapped on the homepage. This name is found in `HomepageTelemetry.ItemType`.
2356+
labels:
2357+
- top_sites
2358+
- jump_back_in
2359+
- bookmarks
2360+
- stories
2361+
- customize_homepage
23652362
bugs:
2366-
- https://github.com/mozilla-mobile/firefox-ios/issues/25083
2363+
- https://github.com/mozilla-mobile/firefox-ios/issues/26216
23672364
data_reviews:
2368-
- https://github.com/mozilla-mobile/firefox-ios/pull/26144
2365+
- https://github.com/mozilla-mobile/firefox-ios/pull/xxxxx
23692366
notification_emails:
23702367
23712368
expires: "2025-07-01"

0 commit comments

Comments
 (0)