Add Email Aliases metrics (#36469)
* Add metrics for Email Aliases * Add notes and copy metrics for email aliases * Presubmit fix * fix ups * Add tests * Address PR feedback * Pass metrics remote as a prop
This commit is contained in:
@@ -643,7 +643,8 @@ void BraveContentBrowserClient::RegisterTrustedWebUIInterfaceBrokers(
|
||||
#if BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
if (email_aliases::features::IsEmailAliasesEnabled()) {
|
||||
registry.ForWebUI<BraveSettingsUI>()
|
||||
.Add<email_aliases::mojom::EmailAliasesService>();
|
||||
.Add<email_aliases::mojom::EmailAliasesService>()
|
||||
.Add<email_aliases::mojom::EmailAliasesMetrics>();
|
||||
}
|
||||
#endif
|
||||
if (brave_account::features::IsBraveAccountEnabled()) {
|
||||
|
||||
@@ -774,7 +774,8 @@ bool BraveBrowserCommandController::ExecuteBraveCommandWithDisposition(
|
||||
break;
|
||||
#if BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
case IDC_SHOW_EMAIL_ALIASES:
|
||||
browser_->GetFeatures().email_aliases_controller()->OpenSettingsPage();
|
||||
browser_->GetFeatures().email_aliases_controller()->OpenSettingsPage(
|
||||
email_aliases::SettingsPageMethod::kAppMenu);
|
||||
break;
|
||||
#endif
|
||||
#if BUILDFLAG(ENABLE_CONTAINERS)
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include "brave/browser/ui/email_aliases/email_aliases_controller.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "base/check_is_test.h"
|
||||
#include "brave/browser/ui/webui/email_aliases/email_aliases_panel_ui.h"
|
||||
#include "brave/components/constants/webui_url_constants.h"
|
||||
#include "brave/components/email_aliases/constants.h"
|
||||
#include "chrome/browser/ui/singleton_tabs.h"
|
||||
#include "chrome/browser/ui/views/frame/browser_view.h"
|
||||
#include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
|
||||
@@ -24,7 +26,6 @@
|
||||
namespace {
|
||||
inline constexpr char kEmailAliasesPanelURL[] = "chrome://email-aliases.panel/";
|
||||
|
||||
constexpr char kEmailAliasesSettingsURL[] = "brave://settings/email-aliases";
|
||||
constexpr int kDialogWidth = 512;
|
||||
constexpr gfx::Size kDialogMinSize(kDialogWidth, 336);
|
||||
constexpr gfx::Size kDialogMaxSize(kDialogWidth, 794);
|
||||
@@ -162,11 +163,13 @@ bool EmailAliasesController::IsAvailableFor(
|
||||
params.form_control_type == FormControlType::kInputText;
|
||||
}
|
||||
|
||||
void EmailAliasesController::ShowBubble(content::WebContents* initiator,
|
||||
content::RenderFrameHost* render_frame,
|
||||
uint64_t field_renderer_id) {
|
||||
void EmailAliasesController::ShowBubble(
|
||||
content::WebContents* initiator,
|
||||
content::RenderFrameHost* render_frame,
|
||||
uint64_t field_renderer_id,
|
||||
std::optional<SettingsPageMethod> method) {
|
||||
if (!email_aliases_service_->IsAuthenticated()) {
|
||||
return OpenSettingsPage();
|
||||
return OpenSettingsPage(method);
|
||||
}
|
||||
|
||||
CloseBubble();
|
||||
@@ -186,7 +189,11 @@ void EmailAliasesController::CloseBubble() {
|
||||
bubble_->GetWebDialogDelegate()->OnDialogClosed({});
|
||||
}
|
||||
|
||||
void EmailAliasesController::OpenSettingsPage() {
|
||||
void EmailAliasesController::OpenSettingsPage(
|
||||
std::optional<SettingsPageMethod> method) {
|
||||
if (method) {
|
||||
email_aliases_service_->metrics().RecordSettingsPageNavigation(*method);
|
||||
}
|
||||
ShowSingletonTabOverwritingNTP(browser_view_->browser(),
|
||||
GURL(kEmailAliasesSettingsURL));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
#ifndef BRAVE_BROWSER_UI_EMAIL_ALIASES_EMAIL_ALIASES_CONTROLLER_H_
|
||||
#define BRAVE_BROWSER_UI_EMAIL_ALIASES_EMAIL_ALIASES_CONTROLLER_H_
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "brave/components/email_aliases/email_aliases_metrics.h"
|
||||
#include "brave/components/email_aliases/email_aliases_service.h"
|
||||
|
||||
class BrowserView;
|
||||
@@ -33,9 +36,11 @@ class EmailAliasesController {
|
||||
|
||||
void ShowBubble(content::WebContents* initiator,
|
||||
content::RenderFrameHost* render_frame,
|
||||
uint64_t field_renderer_id);
|
||||
uint64_t field_renderer_id,
|
||||
std::optional<SettingsPageMethod> method = std::nullopt);
|
||||
void CloseBubble();
|
||||
void OpenSettingsPage();
|
||||
void OpenSettingsPage(
|
||||
std::optional<SettingsPageMethod> method = std::nullopt);
|
||||
|
||||
content::WebContents* GetBubbleForTesting();
|
||||
static void DisableAutoCloseBubbleForTesting(bool disale_autoclose);
|
||||
|
||||
@@ -36,6 +36,14 @@
|
||||
#include "brave/components/ai_chat/core/browser/ai_chat_metrics.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
#include "brave/browser/email_aliases/email_aliases_service_factory.h"
|
||||
#include "brave/components/email_aliases/constants.h"
|
||||
#include "brave/components/email_aliases/email_aliases_metrics.h"
|
||||
#include "brave/components/email_aliases/email_aliases_service.h"
|
||||
#include "brave/components/email_aliases/features.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using brave_search_conversion::ConversionType;
|
||||
@@ -106,6 +114,17 @@ BraveOmniboxClientImpl::BraveOmniboxClientImpl(LocationBar* location_bar,
|
||||
}
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
if (email_aliases::features::IsEmailAliasesEnabled()) {
|
||||
auto* email_aliases_service =
|
||||
email_aliases::EmailAliasesServiceFactory::GetServiceForProfile(
|
||||
profile);
|
||||
if (email_aliases_service) {
|
||||
email_aliases_metrics_ = &email_aliases_service->metrics();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
pref_change_registrar_.Init(profile_->GetPrefs());
|
||||
pref_change_registrar_.Add(
|
||||
brave_rewards::prefs::kEnabled,
|
||||
@@ -191,6 +210,14 @@ void BraveOmniboxClientImpl::OnAutocompleteAccept(
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
if (email_aliases_metrics_ &&
|
||||
match.type == AutocompleteMatchType::URL_WHAT_YOU_TYPED &&
|
||||
destination_url.spec() == email_aliases::kEmailAliasesSettingsURL) {
|
||||
email_aliases_metrics_->RecordSettingsPageNavigation(
|
||||
email_aliases::SettingsPageMethod::kManualNavigation);
|
||||
}
|
||||
#endif
|
||||
|
||||
ChromeOmniboxClient::OnAutocompleteAccept(
|
||||
destination_url, post_content, disposition, transition, match_type,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "brave/browser/autocomplete/brave_autocomplete_scheme_classifier.h"
|
||||
#include "brave/components/email_aliases/buildflags/buildflags.h"
|
||||
#include "brave/components/time_period_storage/weekly_storage.h"
|
||||
#include "chrome/browser/ui/omnibox/chrome_omnibox_client.h"
|
||||
#include "components/prefs/pref_change_registrar.h"
|
||||
@@ -20,6 +21,10 @@ namespace ai_chat {
|
||||
class AIChatMetrics;
|
||||
} // namespace ai_chat
|
||||
|
||||
namespace email_aliases {
|
||||
class EmailAliasesMetrics;
|
||||
} // namespace email_aliases
|
||||
|
||||
namespace misc_metrics {
|
||||
class BraveSearchMetrics;
|
||||
class NavigationSourceMetrics;
|
||||
@@ -62,6 +67,9 @@ class BraveOmniboxClientImpl : public ChromeOmniboxClient {
|
||||
raw_ptr<misc_metrics::BraveSearchMetrics> brave_search_metrics_ = nullptr;
|
||||
raw_ptr<misc_metrics::NavigationSourceMetrics> navigation_source_metrics_ =
|
||||
nullptr;
|
||||
#if BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
raw_ptr<email_aliases::EmailAliasesMetrics> email_aliases_metrics_ = nullptr;
|
||||
#endif
|
||||
BraveAutocompleteSchemeClassifier scheme_classifier_;
|
||||
WeeklyStorage search_storage_;
|
||||
PrefChangeRegistrar pref_change_registrar_;
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
#if BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
#include "brave/browser/email_aliases/email_aliases_service_factory.h"
|
||||
#include "brave/components/email_aliases/email_aliases.mojom.h"
|
||||
#include "brave/components/email_aliases/email_aliases_service.h"
|
||||
#include "brave/components/email_aliases/features.h"
|
||||
#endif
|
||||
|
||||
@@ -413,6 +414,16 @@ void BraveSettingsUI::BindInterface(
|
||||
email_aliases::EmailAliasesServiceFactory::BindForProfile(
|
||||
profile, std::move(receiver));
|
||||
}
|
||||
|
||||
void BraveSettingsUI::BindInterface(
|
||||
mojo::PendingReceiver<email_aliases::mojom::EmailAliasesMetrics> receiver) {
|
||||
auto* profile = Profile::FromWebUI(web_ui());
|
||||
auto* service =
|
||||
email_aliases::EmailAliasesServiceFactory::GetServiceForProfile(profile);
|
||||
if (service) {
|
||||
service->metrics().BindInterface(std::move(receiver));
|
||||
}
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
|
||||
void BraveSettingsUI::BindInterface(
|
||||
|
||||
@@ -90,6 +90,9 @@ class BraveSettingsUI : public settings::SettingsUI {
|
||||
void BindInterface(
|
||||
mojo::PendingReceiver<email_aliases::mojom::EmailAliasesService>
|
||||
pending_receiver);
|
||||
void BindInterface(
|
||||
mojo::PendingReceiver<email_aliases::mojom::EmailAliasesMetrics>
|
||||
pending_receiver);
|
||||
#endif
|
||||
|
||||
void BindInterface(
|
||||
|
||||
@@ -455,8 +455,10 @@ void RenderViewContextMenu::ExecuteCommand(int id, int event_flags) {
|
||||
#if BUILDFLAG(ENABLE_EMAIL_ALIASES)
|
||||
case IDC_NEW_EMAIL_ALIAS:
|
||||
if (auto* email_aliases = GetEmailAliasesController(GetBrowser())) {
|
||||
email_aliases->ShowBubble(source_web_contents_, GetRenderFrameHost(),
|
||||
params_.field_renderer_id);
|
||||
email_aliases->ShowBubble(
|
||||
source_web_contents_, GetRenderFrameHost(),
|
||||
params_.field_renderer_id,
|
||||
email_aliases::SettingsPageMethod::kContextMenu);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
@@ -164,8 +164,9 @@ class BraveChromeAutofillClient : public ChromeAutofillClient {
|
||||
auto* render_frame_host = autofill::FindRenderFrameHostByToken(
|
||||
*web_contents(), field.frame_token);
|
||||
if (render_frame_host) {
|
||||
email_aliases->ShowBubble(web_contents(), render_frame_host,
|
||||
field.renderer_id.value());
|
||||
email_aliases->ShowBubble(
|
||||
web_contents(), render_frame_host, field.renderer_id.value(),
|
||||
email_aliases::SettingsPageMethod::kAutofillBubble);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -47,9 +47,12 @@ static_library("features") {
|
||||
|
||||
static_library("service") {
|
||||
sources = [
|
||||
"constants.h",
|
||||
"email_aliases_auth.cc",
|
||||
"email_aliases_auth.h",
|
||||
"email_aliases_endpoints.h",
|
||||
"email_aliases_metrics.cc",
|
||||
"email_aliases_metrics.h",
|
||||
"email_aliases_notes.cc",
|
||||
"email_aliases_notes.h",
|
||||
"email_aliases_service.cc",
|
||||
@@ -67,7 +70,9 @@ static_library("service") {
|
||||
"//brave/components/brave_account/mojom",
|
||||
"//brave/components/constants",
|
||||
"//brave/components/email_aliases:features",
|
||||
"//brave/components/p3a_utils",
|
||||
"//brave/components/resources:strings_grit",
|
||||
"//brave/components/time_period_storage",
|
||||
"//components/keyed_service/core",
|
||||
"//components/pref_registry",
|
||||
"//components/prefs",
|
||||
@@ -101,6 +106,7 @@ source_set("test_utils") {
|
||||
source_set("unit_tests") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"email_aliases_metrics_unittest.cc",
|
||||
"email_aliases_notes_unittest.cc",
|
||||
"email_aliases_service_unittest.cc",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_EMAIL_ALIASES_CONSTANTS_H_
|
||||
#define BRAVE_COMPONENTS_EMAIL_ALIASES_CONSTANTS_H_
|
||||
|
||||
namespace email_aliases {
|
||||
|
||||
inline constexpr char kEmailAliasesSettingsURL[] =
|
||||
"brave://settings/email-aliases";
|
||||
|
||||
} // namespace email_aliases
|
||||
|
||||
#endif // BRAVE_COMPONENTS_EMAIL_ALIASES_CONSTANTS_H_
|
||||
@@ -71,6 +71,12 @@ interface EmailAliasesService {
|
||||
AddObserver(pending_remote<EmailAliasesServiceObserver> observer);
|
||||
};
|
||||
|
||||
// Interface for reporting email aliases metrics.
|
||||
interface EmailAliasesMetrics {
|
||||
// Record that the user copied an alias to the clipboard.
|
||||
OnAliasCopied();
|
||||
};
|
||||
|
||||
// Interface for handling `Generate new alias` dialog actions
|
||||
// on the browser side.
|
||||
interface EmailAliasesPanelHandler {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/email_aliases/email_aliases_metrics.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "base/time/time.h"
|
||||
#include "brave/components/email_aliases/email_aliases_notes.h"
|
||||
#include "brave/components/email_aliases/pref_names.h"
|
||||
#include "brave/components/p3a_utils/bucket.h"
|
||||
#include "components/prefs/pref_change_registrar.h"
|
||||
#include "components/prefs/pref_registry_simple.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
|
||||
namespace email_aliases {
|
||||
|
||||
namespace {
|
||||
constexpr int kCountBuckets[] = {0, 5, 15};
|
||||
} // namespace
|
||||
|
||||
EmailAliasesMetrics::EmailAliasesMetrics(PrefService& pref_service)
|
||||
: pref_service_(pref_service),
|
||||
clipboard_copy_storage_(&pref_service,
|
||||
prefs::kClipboardCopyCountStorage) {
|
||||
pref_change_registrar_.Init(&pref_service);
|
||||
pref_change_registrar_.Add(
|
||||
prefs::kEmailAliasesNotes,
|
||||
base::BindRepeating(&EmailAliasesMetrics::ReportNotesCount,
|
||||
base::Unretained(this)));
|
||||
ReportAllMetrics();
|
||||
}
|
||||
|
||||
EmailAliasesMetrics::~EmailAliasesMetrics() = default;
|
||||
|
||||
// static
|
||||
void EmailAliasesMetrics::RegisterProfilePrefs(PrefRegistrySimple* registry) {
|
||||
registry->RegisterBooleanPref(prefs::kAliasesPresent, false);
|
||||
registry->RegisterListPref(prefs::kClipboardCopyCountStorage);
|
||||
registry->RegisterBooleanPref(prefs::kSettingsPageMethodReported, false);
|
||||
}
|
||||
|
||||
void EmailAliasesMetrics::RecordSettingsPageNavigation(
|
||||
SettingsPageMethod method) {
|
||||
if (pref_service_->GetBoolean(prefs::kSettingsPageMethodReported)) {
|
||||
return;
|
||||
}
|
||||
UMA_HISTOGRAM_ENUMERATION(kSettingsPageMethodHistogramName, method);
|
||||
pref_service_->SetBoolean(prefs::kSettingsPageMethodReported, true);
|
||||
}
|
||||
|
||||
void EmailAliasesMetrics::BindInterface(
|
||||
mojo::PendingReceiver<mojom::EmailAliasesMetrics> receiver) {
|
||||
receivers_.Add(this, std::move(receiver));
|
||||
}
|
||||
|
||||
void EmailAliasesMetrics::ReportEmailAliasPresence(bool is_present) {
|
||||
pref_service_->SetBoolean(prefs::kAliasesPresent, is_present);
|
||||
if (!is_present) {
|
||||
return;
|
||||
}
|
||||
// Suppress the settings page navigation metric for existing users who already
|
||||
// have aliases, to avoid attributing prior opt-ins.
|
||||
if (!pref_service_->GetBoolean(prefs::kSettingsPageMethodReported)) {
|
||||
pref_service_->SetBoolean(prefs::kSettingsPageMethodReported, true);
|
||||
}
|
||||
ReportAllMetrics();
|
||||
}
|
||||
|
||||
void EmailAliasesMetrics::OnAliasCopied() {
|
||||
clipboard_copy_storage_.AddDelta(1u);
|
||||
ReportCopyCount();
|
||||
}
|
||||
|
||||
void EmailAliasesMetrics::ReportAllMetrics() {
|
||||
report_timer_.Start(FROM_HERE, base::Time::Now() + base::Days(1),
|
||||
base::BindOnce(&EmailAliasesMetrics::ReportAllMetrics,
|
||||
base::Unretained(this)));
|
||||
ReportCopyCount();
|
||||
ReportNotesCount();
|
||||
}
|
||||
|
||||
void EmailAliasesMetrics::ReportCopyCount() {
|
||||
uint64_t total = clipboard_copy_storage_.GetWeeklySum();
|
||||
if (total == 0 ||
|
||||
total > static_cast<uint64_t>(std::numeric_limits<int>::max())) {
|
||||
return;
|
||||
}
|
||||
p3a_utils::RecordToHistogramBucket(kClipboardCopyCountHistogramName,
|
||||
kCountBuckets, static_cast<int>(total));
|
||||
}
|
||||
|
||||
void EmailAliasesMetrics::ReportNotesCount() {
|
||||
if (!pref_service_->GetBoolean(prefs::kAliasesPresent)) {
|
||||
return;
|
||||
}
|
||||
size_t count = EmailAliasesNotes::GetTotalCount(*pref_service_);
|
||||
if (count > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return;
|
||||
}
|
||||
p3a_utils::RecordToHistogramBucket(kNotesCountHistogramName, kCountBuckets,
|
||||
static_cast<int>(count));
|
||||
}
|
||||
|
||||
} // namespace email_aliases
|
||||
@@ -0,0 +1,70 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_EMAIL_ALIASES_EMAIL_ALIASES_METRICS_H_
|
||||
#define BRAVE_COMPONENTS_EMAIL_ALIASES_EMAIL_ALIASES_METRICS_H_
|
||||
|
||||
#include "base/memory/raw_ref.h"
|
||||
#include "base/timer/wall_clock_timer.h"
|
||||
#include "brave/components/email_aliases/email_aliases.mojom.h"
|
||||
#include "brave/components/time_period_storage/weekly_storage.h"
|
||||
#include "components/prefs/pref_change_registrar.h"
|
||||
#include "mojo/public/cpp/bindings/pending_receiver.h"
|
||||
#include "mojo/public/cpp/bindings/receiver_set.h"
|
||||
|
||||
class PrefRegistrySimple;
|
||||
class PrefService;
|
||||
|
||||
namespace email_aliases {
|
||||
|
||||
inline constexpr char kClipboardCopyCountHistogramName[] =
|
||||
"Brave.EmailAliases.ClipboardCopyCount";
|
||||
inline constexpr char kNotesCountHistogramName[] =
|
||||
"Brave.EmailAliases.NotesCount";
|
||||
inline constexpr char kSettingsPageMethodHistogramName[] =
|
||||
"Brave.EmailAliases.SettingsPageMethod";
|
||||
|
||||
enum class SettingsPageMethod {
|
||||
kContextMenu = 0,
|
||||
kAutofillBubble = 1,
|
||||
kAppMenu = 2,
|
||||
kManualNavigation = 3,
|
||||
kMaxValue = kManualNavigation,
|
||||
};
|
||||
|
||||
class EmailAliasesMetrics : public mojom::EmailAliasesMetrics {
|
||||
public:
|
||||
explicit EmailAliasesMetrics(PrefService& pref_service);
|
||||
~EmailAliasesMetrics() override;
|
||||
|
||||
EmailAliasesMetrics(const EmailAliasesMetrics&) = delete;
|
||||
EmailAliasesMetrics& operator=(const EmailAliasesMetrics&) = delete;
|
||||
|
||||
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
|
||||
|
||||
void BindInterface(
|
||||
mojo::PendingReceiver<mojom::EmailAliasesMetrics> receiver);
|
||||
|
||||
// mojom::EmailAliasesMetrics:
|
||||
void OnAliasCopied() override;
|
||||
|
||||
void RecordSettingsPageNavigation(SettingsPageMethod method);
|
||||
void ReportEmailAliasPresence(bool is_present);
|
||||
|
||||
private:
|
||||
void ReportAllMetrics();
|
||||
void ReportCopyCount();
|
||||
void ReportNotesCount();
|
||||
|
||||
const raw_ref<PrefService> pref_service_;
|
||||
WeeklyStorage clipboard_copy_storage_;
|
||||
PrefChangeRegistrar pref_change_registrar_;
|
||||
base::WallClockTimer report_timer_;
|
||||
mojo::ReceiverSet<mojom::EmailAliasesMetrics> receivers_;
|
||||
};
|
||||
|
||||
} // namespace email_aliases
|
||||
|
||||
#endif // BRAVE_COMPONENTS_EMAIL_ALIASES_EMAIL_ALIASES_METRICS_H_
|
||||
@@ -0,0 +1,108 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/email_aliases/email_aliases_metrics.h"
|
||||
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/test/metrics/histogram_tester.h"
|
||||
#include "base/test/task_environment.h"
|
||||
#include "brave/components/email_aliases/email_aliases_notes.h"
|
||||
#include "brave/components/email_aliases/pref_names.h"
|
||||
#include "components/prefs/testing_pref_service.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace email_aliases {
|
||||
|
||||
class EmailAliasesMetricsTest : public testing::Test {
|
||||
public:
|
||||
EmailAliasesMetricsTest()
|
||||
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
|
||||
|
||||
void SetUp() override {
|
||||
EmailAliasesMetrics::RegisterProfilePrefs(pref_service_.registry());
|
||||
EmailAliasesNotes::RegisterProfilePrefs(pref_service_.registry());
|
||||
metrics_ = std::make_unique<EmailAliasesMetrics>(pref_service_);
|
||||
}
|
||||
|
||||
protected:
|
||||
base::test::TaskEnvironment task_environment_;
|
||||
TestingPrefServiceSimple pref_service_;
|
||||
base::HistogramTester histogram_tester_;
|
||||
std::unique_ptr<EmailAliasesMetrics> metrics_;
|
||||
};
|
||||
|
||||
TEST_F(EmailAliasesMetricsTest, NotesCount) {
|
||||
// With aliases present and no notes, reports bucket 0.
|
||||
metrics_->ReportEmailAliasPresence(true);
|
||||
histogram_tester_.ExpectUniqueSample(kNotesCountHistogramName, 0, 1);
|
||||
|
||||
// 1 note = bucket 1 (1-5).
|
||||
EmailAliasesNotes notes(pref_service_, "user@example.com");
|
||||
notes.UpdateNote("alias@example.com", "note");
|
||||
histogram_tester_.ExpectBucketCount(kNotesCountHistogramName, 1, 1);
|
||||
|
||||
// 6 notes = bucket 2 (6-15).
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
notes.UpdateNote("alias" + base::NumberToString(i) + "@example.com",
|
||||
"note");
|
||||
}
|
||||
histogram_tester_.ExpectBucketCount(kNotesCountHistogramName, 2, 1);
|
||||
|
||||
// Without aliases present, further note changes should not report.
|
||||
metrics_->ReportEmailAliasPresence(false);
|
||||
histogram_tester_.ExpectTotalCount(kNotesCountHistogramName, 7);
|
||||
notes.UpdateNote("alias100@example.com", "note");
|
||||
histogram_tester_.ExpectTotalCount(kNotesCountHistogramName, 7);
|
||||
}
|
||||
|
||||
TEST_F(EmailAliasesMetricsTest, CopyCount) {
|
||||
metrics_->ReportEmailAliasPresence(true);
|
||||
histogram_tester_.ExpectTotalCount(kClipboardCopyCountHistogramName, 0);
|
||||
|
||||
// 3 copies = bucket 1 (1-5). Running sum recorded each call:
|
||||
// call 1: sum=1 -> bucket 1, call 2: sum=2 -> bucket 1, call 3: sum=3 ->
|
||||
// bucket 1.
|
||||
metrics_->OnAliasCopied();
|
||||
metrics_->OnAliasCopied();
|
||||
metrics_->OnAliasCopied();
|
||||
histogram_tester_.ExpectUniqueSample(kClipboardCopyCountHistogramName, 1, 3);
|
||||
|
||||
// 6 more copies pushes sum to 9 = bucket 2 (6-15).
|
||||
for (int i = 0; i < 6; i++) {
|
||||
metrics_->OnAliasCopied();
|
||||
}
|
||||
histogram_tester_.ExpectBucketCount(kClipboardCopyCountHistogramName, 2, 4);
|
||||
|
||||
// Timer fires daily for 14 days — 7 additional reports while in weekly
|
||||
// window.
|
||||
task_environment_.FastForwardBy(base::Days(14));
|
||||
histogram_tester_.ExpectTotalCount(kClipboardCopyCountHistogramName, 15);
|
||||
}
|
||||
|
||||
TEST_F(EmailAliasesMetricsTest, SettingsPageNavigation) {
|
||||
metrics_->RecordSettingsPageNavigation(SettingsPageMethod::kManualNavigation);
|
||||
histogram_tester_.ExpectUniqueSample(kSettingsPageMethodHistogramName,
|
||||
SettingsPageMethod::kManualNavigation,
|
||||
1);
|
||||
|
||||
// Second call and existing users with aliases should be suppressed.
|
||||
metrics_->RecordSettingsPageNavigation(SettingsPageMethod::kAppMenu);
|
||||
histogram_tester_.ExpectTotalCount(kSettingsPageMethodHistogramName, 1);
|
||||
|
||||
metrics_->ReportEmailAliasPresence(true);
|
||||
metrics_->RecordSettingsPageNavigation(SettingsPageMethod::kAppMenu);
|
||||
histogram_tester_.ExpectTotalCount(kSettingsPageMethodHistogramName, 1);
|
||||
}
|
||||
|
||||
TEST_F(EmailAliasesMetricsTest, ReportAllMetricsOnTimer) {
|
||||
metrics_->ReportEmailAliasPresence(true);
|
||||
histogram_tester_.ExpectTotalCount(kNotesCountHistogramName, 1);
|
||||
|
||||
task_environment_.FastForwardBy(base::Days(1));
|
||||
|
||||
histogram_tester_.ExpectTotalCount(kNotesCountHistogramName, 2);
|
||||
}
|
||||
|
||||
} // namespace email_aliases
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "brave/components/email_aliases/email_aliases_notes.h"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#include "brave/components/email_aliases/pref_names.h"
|
||||
#include "components/pref_registry/pref_registry_syncable.h"
|
||||
#include "components/prefs/pref_registry_simple.h"
|
||||
@@ -26,6 +28,16 @@ void EmailAliasesNotes::RegisterProfilePrefs(PrefRegistrySimple* registry) {
|
||||
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
|
||||
}
|
||||
|
||||
// static
|
||||
size_t EmailAliasesNotes::GetTotalCount(PrefService& pref_service) {
|
||||
const auto& pref = pref_service.GetDict(prefs::kEmailAliasesNotes);
|
||||
return std::reduce(
|
||||
pref.begin(), pref.end(), size_t{0}, [](size_t acc, const auto& entry) {
|
||||
return acc +
|
||||
(entry.second.is_dict() ? entry.second.GetDict().size() : 0);
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<std::string> EmailAliasesNotes::GetNote(
|
||||
const std::string& alias) {
|
||||
const auto& pref = pref_service_->GetDict(prefs::kEmailAliasesNotes);
|
||||
|
||||
@@ -26,6 +26,7 @@ class EmailAliasesNotes {
|
||||
|
||||
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
|
||||
|
||||
static size_t GetTotalCount(PrefService& pref_service);
|
||||
std::optional<std::string> GetNote(const std::string& alias);
|
||||
void UpdateNote(const std::string& alias, const std::string& notes);
|
||||
void RemoveNote(const std::string& alias);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "brave/components/constants/network_constants.h"
|
||||
#include "brave/components/email_aliases/email_aliases.mojom.h"
|
||||
#include "brave/components/email_aliases/email_aliases_api.h"
|
||||
#include "brave/components/email_aliases/email_aliases_metrics.h"
|
||||
#include "brave/components/email_aliases/email_aliases_notes.h"
|
||||
#include "brave/components/email_aliases/features.h"
|
||||
#include "brave/components/email_aliases/pref_names.h"
|
||||
@@ -104,7 +105,9 @@ EmailAliasesService::EmailAliasesService(
|
||||
brave_account_auth,
|
||||
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
|
||||
PrefService& pref_service)
|
||||
: url_loader_factory_(url_loader_factory), pref_service_(pref_service) {
|
||||
: url_loader_factory_(url_loader_factory),
|
||||
pref_service_(pref_service),
|
||||
metrics_(pref_service) {
|
||||
CHECK(base::FeatureList::IsEnabled(email_aliases::features::kEmailAliases));
|
||||
CHECK(brave_account_auth);
|
||||
|
||||
@@ -118,6 +121,7 @@ EmailAliasesService::~EmailAliasesService() = default;
|
||||
// static
|
||||
void EmailAliasesService::RegisterProfilePrefs(PrefRegistrySimple* registry) {
|
||||
registry->RegisterBooleanPref(prefs::kEmailAliasesEnabled, true);
|
||||
EmailAliasesMetrics::RegisterProfilePrefs(registry);
|
||||
EmailAliasesNotes::RegisterProfilePrefs(registry);
|
||||
}
|
||||
|
||||
@@ -211,6 +215,9 @@ void EmailAliasesService::OnGenerateAliasResponse(
|
||||
parsed.has_value()
|
||||
? base::expected<std::string, std::string>(parsed.value().alias)
|
||||
: base::unexpected(parsed.error());
|
||||
if (result.has_value()) {
|
||||
metrics_.ReportEmailAliasPresence(true);
|
||||
}
|
||||
std::move(user_callback).Run(std::move(result));
|
||||
}
|
||||
|
||||
@@ -359,6 +366,7 @@ void EmailAliasesService::OnRefreshAliasesResponse(
|
||||
aliases.push_back(std::move(alias_obj));
|
||||
}
|
||||
|
||||
metrics_.ReportEmailAliasPresence(!aliases.empty());
|
||||
NotifyObserversAliasesUpdated(
|
||||
observers_, mojom::AliasesUpdate::NewAliases(std::move(aliases)));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "brave/components/email_aliases/email_aliases.mojom.h"
|
||||
#include "brave/components/email_aliases/email_aliases_auth.h"
|
||||
#include "brave/components/email_aliases/email_aliases_endpoints.h"
|
||||
#include "brave/components/email_aliases/email_aliases_metrics.h"
|
||||
#include "components/keyed_service/core/keyed_service.h"
|
||||
#include "mojo/public/cpp/bindings/pending_receiver.h"
|
||||
#include "mojo/public/cpp/bindings/pending_remote.h"
|
||||
@@ -82,6 +83,8 @@ class EmailAliasesService : public KeyedService,
|
||||
|
||||
EmailAliasesAuth* GetAuth();
|
||||
|
||||
EmailAliasesMetrics& metrics() { return metrics_; }
|
||||
|
||||
private:
|
||||
using TokenResult =
|
||||
base::expected<brave_account::mojom::GetServiceTokenResultPtr,
|
||||
@@ -134,6 +137,8 @@ class EmailAliasesService : public KeyedService,
|
||||
|
||||
const raw_ref<PrefService> pref_service_;
|
||||
|
||||
EmailAliasesMetrics metrics_;
|
||||
|
||||
// WeakPtrFactory to safely bind callbacks across async network operations.
|
||||
base::WeakPtrFactory<EmailAliasesService> weak_factory_{this};
|
||||
};
|
||||
|
||||
@@ -13,6 +13,17 @@ inline constexpr char kEmailAliasesEnabled[] = "brave.email_aliases.enabled";
|
||||
// Preference key for storing the aliases notes.
|
||||
inline constexpr char kEmailAliasesNotes[] = "brave.email_aliases.notes";
|
||||
|
||||
// Set to true once the user has at least one alias.
|
||||
inline constexpr char kAliasesPresent[] = "brave.email_aliases.aliases_present";
|
||||
|
||||
// Weekly storage list for clipboard copy count metric.
|
||||
inline constexpr char kClipboardCopyCountStorage[] =
|
||||
"brave.email_aliases.clipboard_copy_count_storage";
|
||||
|
||||
// Set to true once the settings page access method metric has been recorded.
|
||||
inline constexpr char kSettingsPageMethodReported[] =
|
||||
"brave.email_aliases.settings_page_method_reported";
|
||||
|
||||
} // namespace email_aliases::prefs
|
||||
|
||||
#endif // BRAVE_COMPONENTS_EMAIL_ALIASES_PREF_NAMES_H_
|
||||
|
||||
@@ -70,15 +70,18 @@ const AliasMenuItem = ({
|
||||
const CopyToast = ({
|
||||
text,
|
||||
tabIndex,
|
||||
onCopy,
|
||||
children,
|
||||
}: {
|
||||
text: string
|
||||
tabIndex?: number
|
||||
onCopy?: () => void
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const [copied, setCopied] = React.useState<boolean>(false)
|
||||
const copy = () => {
|
||||
navigator.clipboard.writeText(text)
|
||||
onCopy?.()
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 1000)
|
||||
}
|
||||
@@ -105,16 +108,21 @@ const CopyToast = ({
|
||||
|
||||
export const AliasItem = ({
|
||||
alias,
|
||||
onCopy,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: {
|
||||
alias: Alias
|
||||
onCopy: () => void
|
||||
onEdit: () => void
|
||||
onDelete: () => void
|
||||
}) => (
|
||||
<AliasItemRow>
|
||||
<Col>
|
||||
<CopyToast text={alias.email}>
|
||||
<CopyToast
|
||||
text={alias.email}
|
||||
onCopy={onCopy}
|
||||
>
|
||||
<EmailContainer
|
||||
title={getLocale(S.SETTINGS_EMAIL_ALIASES_CLICK_TO_COPY_ALIAS)}
|
||||
>
|
||||
@@ -136,6 +144,7 @@ export const AliasItem = ({
|
||||
<CopyToast
|
||||
text={alias.email}
|
||||
tabIndex={0}
|
||||
onCopy={onCopy}
|
||||
>
|
||||
<Button
|
||||
fab
|
||||
|
||||
@@ -20,6 +20,7 @@ import Row from './styles/Row'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
Alias,
|
||||
EmailAliasesMetricsRemote,
|
||||
EmailAliasesServiceInterface,
|
||||
MAX_ALIASES,
|
||||
} from 'gen/brave/components/email_aliases/email_aliases.mojom.m'
|
||||
@@ -70,10 +71,12 @@ export const AliasList = ({
|
||||
aliases,
|
||||
authEmail,
|
||||
emailAliasesService,
|
||||
metrics,
|
||||
}: {
|
||||
emailAliasesService: EmailAliasesServiceInterface
|
||||
aliases: Alias[]
|
||||
authEmail: string
|
||||
metrics?: EmailAliasesMetricsRemote
|
||||
}) => {
|
||||
const [editState, setEditState] = React.useState<EditState>({ mode: 'None' })
|
||||
return (
|
||||
@@ -84,6 +87,7 @@ export const AliasList = ({
|
||||
<AliasItem
|
||||
key={alias.email}
|
||||
alias={alias}
|
||||
onCopy={() => metrics?.onAliasCopied()}
|
||||
onEdit={() => setEditState({ mode: 'Edit', alias: alias })}
|
||||
onDelete={() => setEditState({ mode: 'Delete', alias: alias })}
|
||||
></AliasItem>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
AliasesUpdate,
|
||||
AuthState,
|
||||
AuthenticationStatus,
|
||||
EmailAliasesMetricsRemote,
|
||||
EmailAliasesServiceInterface,
|
||||
} from 'gen/brave/components/email_aliases/email_aliases.mojom.m'
|
||||
|
||||
@@ -45,10 +46,12 @@ export const ManagePage = ({
|
||||
aliasesUpdate,
|
||||
authState,
|
||||
emailAliasesService,
|
||||
metrics,
|
||||
}: {
|
||||
aliasesUpdate: AliasesUpdate
|
||||
authState: AuthState
|
||||
emailAliasesService: EmailAliasesServiceInterface
|
||||
metrics?: EmailAliasesMetricsRemote
|
||||
}) => (
|
||||
<PageCol>
|
||||
{authState.status === AuthenticationStatus.kAuthenticated
|
||||
@@ -59,6 +62,7 @@ export const ManagePage = ({
|
||||
aliases={aliasesUpdate.aliases!}
|
||||
authEmail={authState.email}
|
||||
emailAliasesService={emailAliasesService}
|
||||
metrics={metrics}
|
||||
/>
|
||||
))}
|
||||
</PageCol>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { StyleSheetManager } from 'styled-components'
|
||||
import * as React from 'react'
|
||||
import { setIconBasePath } from '@brave/leo/react/icon'
|
||||
import {
|
||||
EmailAliasesMetrics,
|
||||
EmailAliasesServiceInterface,
|
||||
EmailAliasesServiceObserverInterface,
|
||||
EmailAliasesServiceObserverReceiver,
|
||||
@@ -19,9 +20,11 @@ import { useEmailAliases } from './content/use_email_aliases'
|
||||
export const ManagePageConnected = ({
|
||||
emailAliasesService,
|
||||
bindObserver,
|
||||
metrics,
|
||||
}: {
|
||||
emailAliasesService: EmailAliasesServiceInterface
|
||||
bindObserver: (observer: EmailAliasesServiceObserverInterface) => () => void
|
||||
metrics?: ReturnType<typeof EmailAliasesMetrics.getRemote>
|
||||
}) => {
|
||||
const { authState, aliasesUpdate } = useEmailAliases(bindObserver)
|
||||
return (
|
||||
@@ -29,6 +32,7 @@ export const ManagePageConnected = ({
|
||||
authState={authState}
|
||||
aliasesUpdate={aliasesUpdate}
|
||||
emailAliasesService={emailAliasesService}
|
||||
metrics={metrics}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -37,6 +41,7 @@ export const mount = (signInElem: HTMLElement, manageElem: HTMLElement) => {
|
||||
setIconBasePath('//resources/brave-icons')
|
||||
|
||||
const emailAliasesService = EmailAliasesService.getRemote()
|
||||
const emailAliasesMetrics = EmailAliasesMetrics.getRemote()
|
||||
|
||||
const bindObserver = (observer: EmailAliasesServiceObserverInterface) => {
|
||||
const observerReceiver = new EmailAliasesServiceObserverReceiver(observer)
|
||||
@@ -60,6 +65,7 @@ export const mount = (signInElem: HTMLElement, manageElem: HTMLElement) => {
|
||||
<ManagePageConnected
|
||||
emailAliasesService={emailAliasesService}
|
||||
bindObserver={bindObserver}
|
||||
metrics={emailAliasesMetrics}
|
||||
/>
|
||||
</StyleSheetManager>,
|
||||
)
|
||||
|
||||
@@ -90,6 +90,9 @@ inline constexpr auto kCollectedTypicalHistograms =
|
||||
{"Brave.DNS.AutoSecureRequests.Quad9.2", MetricConfig{.ephemeral = true}},
|
||||
{"Brave.DNS.AutoSecureRequests.Wikimedia.2", MetricConfig{.ephemeral = true}},
|
||||
{"Brave.DNS.SecureSetting", {}},
|
||||
{"Brave.EmailAliases.ClipboardCopyCount", MetricConfig{.ephemeral = true}},
|
||||
{"Brave.EmailAliases.NotesCount", MetricConfig{.ephemeral = true}},
|
||||
{"Brave.EmailAliases.SettingsPageMethod", MetricConfig{.ephemeral = true}},
|
||||
{"Brave.Extensions.AdBlock", {}},
|
||||
{"Brave.Extensions.SelectManifestV2", {}},
|
||||
{"Brave.IOS.IsLikelyDefault", MetricConfig{
|
||||
|
||||
Reference in New Issue
Block a user