Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add search and sort observability metrics #3007

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2533,8 +2533,12 @@
B6FA893F269C424500588ECD /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */; };
B6FA8941269C425400588ECD /* PrivacyDashboardPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */; };
BB5789722B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; };
BB7B5F982C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7B5F972C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift */; };
BB7B5F992C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7B5F972C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift */; };
BBB881882C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB881872C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift */; };
BBB881892C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB881872C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift */; };
BBBEE1BF2C4FF63600035ABA /* SortBookmarksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBEE1BE2C4FF63600035ABA /* SortBookmarksViewModelTests.swift */; };
BBBEE1C02C4FF63600035ABA /* SortBookmarksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBEE1BE2C4FF63600035ABA /* SortBookmarksViewModelTests.swift */; };
BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */; };
BBDFDC5D2B2B8E2100F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */; };
BBFB727F2C48047C0088884C /* SortBookmarksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFB727E2C48047C0088884C /* SortBookmarksViewModel.swift */; };
Expand Down Expand Up @@ -4215,7 +4219,9 @@
B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = "<group>"; };
B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPopover.swift; sourceTree = "<group>"; };
BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionSubscriptionEventHandler.swift; sourceTree = "<group>"; };
BB7B5F972C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksSearchAndSortMetrics.swift; sourceTree = "<group>"; };
BBB881872C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkListTreeControllerSearchDataSource.swift; sourceTree = "<group>"; };
BBBEE1BE2C4FF63600035ABA /* SortBookmarksViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortBookmarksViewModelTests.swift; sourceTree = "<group>"; };
BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionExternalWaitlistPixels.swift; sourceTree = "<group>"; };
BBFB727E2C48047C0088884C /* SortBookmarksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortBookmarksViewModel.swift; sourceTree = "<group>"; };
BBFF355C2C4AF26200DA3289 /* BookmarksSortModeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksSortModeTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6661,6 +6667,7 @@
9F0FFFB32BCCAE37007C87DD /* BookmarkAllTabsDialogCoordinatorViewModelTests.swift */,
9FA5A0A82BC900FC00153786 /* BookmarkAllTabsDialogViewModelTests.swift */,
BBFF355C2C4AF26200DA3289 /* BookmarksSortModeTests.swift */,
BBBEE1BE2C4FF63600035ABA /* SortBookmarksViewModelTests.swift */,
);
path = ViewModels;
sourceTree = "<group>";
Expand Down Expand Up @@ -7597,6 +7604,7 @@
B6F9BDDB2B45B7EE00677B33 /* WebsiteInfo.swift */,
9F872DA22B90920F00138637 /* BookmarkFolderInfo.swift */,
BBB881872C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift */,
BB7B5F972C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -9878,6 +9886,7 @@
3706FA93293F65D500E42796 /* WKWebView+Download.swift in Sources */,
3706FA94293F65D500E42796 /* TabShadowConfig.swift in Sources */,
3706FA97293F65D500E42796 /* WindowDraggingView.swift in Sources */,
BB7B5F992C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift in Sources */,
B60D644A2AAF1B7C00B26F50 /* AddressBarTextSelectionNavigation.swift in Sources */,
1D01A3D52B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */,
3706FA98293F65D500E42796 /* SecureVaultSorting.swift in Sources */,
Expand Down Expand Up @@ -10776,6 +10785,7 @@
56A0542E2C201DAA007D8FAB /* MockContentBlocking.swift in Sources */,
3706FE1F293F661700E42796 /* AppStateChangePublisherTests.swift in Sources */,
9FA5A0B12BC9039300153786 /* BookmarkFolderStoreMock.swift in Sources */,
BBBEE1C02C4FF63600035ABA /* SortBookmarksViewModelTests.swift in Sources */,
9F0660792BECC81C00B8EEF1 /* PixelCapturedParameters.swift in Sources */,
3706FE20293F661700E42796 /* CLLocationManagerMock.swift in Sources */,
B6656E0E2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */,
Expand Down Expand Up @@ -11323,6 +11333,7 @@
AA80EC54256BE3BC007083E7 /* UserText.swift in Sources */,
B61EF3EC266F91E700B4D78F /* WKWebView+Download.swift in Sources */,
311B262728E73E0A00FD181A /* TabShadowConfig.swift in Sources */,
BB7B5F982C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift in Sources */,
B6BCC54A2AFDF24B002C5499 /* TaskWithProgress.swift in Sources */,
B6DB3AEF278D5C370024C5C4 /* URLSessionExtension.swift in Sources */,
4B7A60A1273E0BE400BBDFEB /* WKWebsiteDataStoreExtension.swift in Sources */,
Expand Down Expand Up @@ -12251,6 +12262,7 @@
F1B8EC7A2C29957A00D395F5 /* SubscriptionFeatureAvailabilityMock.swift in Sources */,
9F872DA02B90644800138637 /* ContextualMenuTests.swift in Sources */,
4B9292BE2667103100AD2C21 /* PasteboardFolderTests.swift in Sources */,
BBBEE1BF2C4FF63600035ABA /* SortBookmarksViewModelTests.swift in Sources */,
4B9292C52667104B00AD2C21 /* CoreDataTestUtilities.swift in Sources */,
4B723E1926B000DC00E14D75 /* TemporaryFileCreator.swift in Sources */,
98EB5D1027516A4800681FE6 /* AppPrivacyConfigurationTests.swift in Sources */,
Expand Down
47 changes: 47 additions & 0 deletions DuckDuckGo/Bookmarks/Model/BookmarksSearchAndSortMetrics.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// BookmarksSearchAndSortMetrics.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import PixelKit

enum BookmarkOperationOrigin: String {
case panel
case manager
}

struct BookmarksSearchAndSortMetrics {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the motivation behind this versus using PixelKit directly?
In general I'd say we use PixelKit directly, and I'd rather keep things consistent unless there's a good reason for it

Copy link
Collaborator Author

@jotaemepereira jotaemepereira Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to encapsulate this kind of logic; if you have multiple fires to the same pixel and need to change something (for example, the frequency), you have only one place to do it.

Finding the pixels related to a specific feature is easier. Now, you need to navigate through the large pixel list in the GeneralPixel enum.

Do you think it’s a blocker for this PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah it's not a blocker. I think your reasoning makes sense, but I would rather do it or not do it universally, and think the consistency is more valuable than the benefits of either approach.

func fireSortButtonClicked(origin: BookmarkOperationOrigin) {
PixelKit.fire(GeneralPixel.bookmarksSortButtonClicked(origin: origin.rawValue))
}

func fireSortButtonDismissed(origin: BookmarkOperationOrigin) {
PixelKit.fire(GeneralPixel.bookmarksSortButtonDismissed(origin: origin.rawValue))
}

func fireSortByName(origin: BookmarkOperationOrigin) {
PixelKit.fire(GeneralPixel.bookmarksSortByName(origin: origin.rawValue))
}

func fireSearchExecuted(origin: BookmarkOperationOrigin) {
PixelKit.fire(GeneralPixel.bookmarksSearchExecuted(origin: origin.rawValue), frequency: .daily)
}

func fireSearchResultClicked(origin: BookmarkOperationOrigin) {
PixelKit.fire(GeneralPixel.bookmarksSearchResultClicked(origin: origin.rawValue))
}
}
23 changes: 18 additions & 5 deletions DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ final class BookmarkListViewController: NSViewController {
private let bookmarkManager: BookmarkManager
private let treeControllerDataSource: BookmarkListTreeControllerDataSource
private let treeControllerSearchDataSource: BookmarkListTreeControllerSearchDataSource
private let sortBookmarksViewModel = SortBookmarksViewModel()
private let sortBookmarksViewModel: SortBookmarksViewModel
private let bookmarkMetrics: BookmarksSearchAndSortMetrics

private lazy var treeController = BookmarkTreeController(dataSource: treeControllerDataSource,
sortMode: sortBookmarksViewModel.selectedSortMode,
Expand Down Expand Up @@ -132,10 +133,13 @@ final class BookmarkListViewController: NSViewController {
return .init(syncService: syncService, syncBookmarksAdapter: syncBookmarksAdapter)
}()

init(bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) {
init(bookmarkManager: BookmarkManager = LocalBookmarkManager.shared,
metrics: BookmarksSearchAndSortMetrics = BookmarksSearchAndSortMetrics()) {
self.bookmarkManager = bookmarkManager
self.treeControllerDataSource = BookmarkListTreeControllerDataSource(bookmarkManager: bookmarkManager)
self.treeControllerSearchDataSource = BookmarkListTreeControllerSearchDataSource(bookmarkManager: bookmarkManager)
self.bookmarkMetrics = metrics
self.sortBookmarksViewModel = SortBookmarksViewModel(metrics: metrics, origin: .panel)
super.init(nibName: nil, bundle: nil)
}

Expand Down Expand Up @@ -482,6 +486,8 @@ final class BookmarkListViewController: NSViewController {

@objc func sortBookmarksButtonClicked(_ sender: NSButton) {
let menu = sortBookmarksViewModel.selectedSortMode.menu
bookmarkMetrics.fireSortButtonClicked(origin: .panel)
menu.delegate = sortBookmarksViewModel
menu.popUpAtMouseLocation(in: sortBookmarksButton)
}

Expand Down Expand Up @@ -527,6 +533,7 @@ final class BookmarkListViewController: NSViewController {
let bookmark = node.representedObject as? Bookmark {
onBookmarkClick(bookmark)
} else if let node = item as? BookmarkNode, let folder = node.representedObject as? BookmarkFolder, dataSource.isSearching {
bookmarkMetrics.fireSearchResultClicked(origin: .panel)
hideSearchBar()
updateSearchAndExpand(folder)
} else {
Expand All @@ -535,6 +542,10 @@ final class BookmarkListViewController: NSViewController {
}

private func onBookmarkClick(_ bookmark: Bookmark) {
if dataSource.isSearching {
bookmarkMetrics.fireSearchResultClicked(origin: .panel)
}

WindowControllersManager.shared.open(bookmark: bookmark)
delegate?.popoverShouldClose(self)
}
Expand Down Expand Up @@ -875,15 +886,15 @@ extension BookmarkListViewController: BookmarkSearchMenuItemSelectors {
extension BookmarkListViewController: BookmarkSortMenuItemSelectors {

func manualSort(_ sender: NSMenuItem) {
sortBookmarksViewModel.selectedSortMode = .manual
sortBookmarksViewModel.setSort(mode: .manual)
}

func sortByNameAscending(_ sender: NSMenuItem) {
sortBookmarksViewModel.selectedSortMode = .nameAscending
sortBookmarksViewModel.setSort(mode: .nameAscending)
}

func sortByNameDescending(_ sender: NSMenuItem) {
sortBookmarksViewModel.selectedSortMode = .nameDescending
sortBookmarksViewModel.setSort(mode: .nameDescending)
}
}

Expand All @@ -900,6 +911,8 @@ extension BookmarkListViewController: NSSearchFieldDelegate {
} else {
showSearch(for: searchQuery)
}

bookmarkMetrics.fireSearchExecuted(origin: .panel)
}
}

Expand Down
42 changes: 35 additions & 7 deletions DuckDuckGo/Bookmarks/ViewModel/SortBookmarksViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ enum BookmarksSortMode: Codable {
}
}

var isNameSorting: Bool {
return self == .nameAscending || self == .nameDescending
}

private func menuItem(for mode: BookmarksSortMode, state: NSControl.StateValue, disabled: Bool = false) -> NSMenuItem {
return NSMenuItem(title: mode.title, action: disabled ? nil : mode.action, state: state)
}
Expand Down Expand Up @@ -123,20 +127,44 @@ final class SortBookmarksUserDefaults: SortBookmarksRepository {
}
}

final class SortBookmarksViewModel {
final class SortBookmarksViewModel: NSObject {

private let metrics: BookmarksSearchAndSortMetrics
private let origin: BookmarkOperationOrigin
private var repository: SortBookmarksRepository

@Published
var selectedSortMode: BookmarksSortMode = .manual {
didSet {
repository.storedSortMode = selectedSortMode
private(set) var selectedSortMode: BookmarksSortMode = .manual
private var wasSortOptionSelected = false

init(repository: SortBookmarksRepository = SortBookmarksUserDefaults(),
metrics: BookmarksSearchAndSortMetrics,
origin: BookmarkOperationOrigin) {
self.metrics = metrics
self.origin = origin
self.repository = repository

selectedSortMode = repository.storedSortMode
}

func setSort(mode: BookmarksSortMode) {
wasSortOptionSelected = true
selectedSortMode = mode
repository.storedSortMode = selectedSortMode

if mode.isNameSorting {
metrics.fireSortByName(origin: origin)
}
}
}

init(repository: SortBookmarksRepository = SortBookmarksUserDefaults()) {
self.repository = repository
extension SortBookmarksViewModel: NSMenuDelegate {

selectedSortMode = repository.storedSortMode
func menuDidClose(_ menu: NSMenu) {
if !wasSortOptionSelected {
metrics.fireSortButtonDismissed(origin: origin)
}

wasSortOptionSelected = false
}
}
21 changes: 21 additions & 0 deletions DuckDuckGo/Statistics/GeneralPixel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,13 @@ enum GeneralPixel: PixelKitEventV2 {
case bookmarksMigrationCouldNotRemoveOldStore
case bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders

// Bookmarks search and sort feature metrics
case bookmarksSortButtonClicked(origin: String)
case bookmarksSortButtonDismissed(origin: String)
case bookmarksSortByName(origin: String)
case bookmarksSearchExecuted(origin: String)
case bookmarksSearchResultClicked(origin: String)

case syncSentUnauthenticatedRequest
case syncMetadataCouldNotLoadDatabase
case syncBookmarksProviderInitializationFailed
Expand Down Expand Up @@ -1009,6 +1016,13 @@ enum GeneralPixel: PixelKitEventV2 {
case .secureVaultKeystoreEventL2KeyPasswordMigration: return "m_mac_secure_vault_keystore_event_l2-key-password-migration"

case .compilationFailed: return "compilation_failed"

// Bookmarks search and sort feature
case .bookmarksSortButtonClicked: return "m_mac_sort_bookmarks_button_clicked"
case .bookmarksSortButtonDismissed: return "m_mac_sort_bookmarks_button_dismissed"
case .bookmarksSortByName: return "m_mac_sort_bookmarks_by_name"
case .bookmarksSearchExecuted: return "m_mac_search_bookmarks_executed"
case .bookmarksSearchResultClicked: return "m_mac_search_result_clicked"
}
}

Expand Down Expand Up @@ -1111,6 +1125,13 @@ enum GeneralPixel: PixelKitEventV2 {
case .onboardingDuckplayerUsed5to7(let cohort):
return [PixelKit.Parameters.experimentCohort: cohort]

case .bookmarksSortButtonClicked(let origin),
.bookmarksSortButtonDismissed(let origin),
.bookmarksSortByName(let origin),
.bookmarksSearchExecuted(let origin),
.bookmarksSearchResultClicked(let origin):
return ["origin": origin]

default: return nil
}
}
Expand Down
Loading
Loading