diff options
author | isherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-13 05:06:47 +0000 |
---|---|---|
committer | isherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-13 05:06:47 +0000 |
commit | 3c33b226aca0de240147a1b95e300d312369b730 (patch) | |
tree | 715c5d1e3038e1cb2917c20d7b88ec2176634735 | |
parent | 9decd75f992e532b8bb5bc205d58dddab739277b (diff) | |
download | chromium_src-3c33b226aca0de240147a1b95e300d312369b730.zip chromium_src-3c33b226aca0de240147a1b95e300d312369b730.tar.gz chromium_src-3c33b226aca0de240147a1b95e300d312369b730.tar.bz2 |
[Autofill] Metrics for dialog UI user interactions.
BUG=165570
Review URL: https://chromiumcodereview.appspot.com/12413007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@194087 0039d316-1c4b-4281-b951-d872f2087c98
14 files changed, 466 insertions, 69 deletions
diff --git a/chrome/browser/ui/autofill/autofill_dialog_controller.h b/chrome/browser/ui/autofill/autofill_dialog_controller.h index 58d0eae..8300760 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_controller.h +++ b/chrome/browser/ui/autofill/autofill_dialog_controller.h @@ -125,12 +125,12 @@ class AutofillDialogController { // Decides whether input of |value| is valid for a field of type |type|. virtual bool InputIsValid(AutofillFieldType type, - const string16& value) = 0; + const string16& value) const = 0; // Decides whether the combination of all |inputs| is valid, returns a // vector of all invalid fields. virtual std::vector<AutofillFieldType> InputsAreValid( - const DetailOutputMap& inputs, ValidationType validation_type) = 0; + const DetailOutputMap& inputs, ValidationType validation_type) const = 0; // Called when the user changes the contents of a text field or activates it // (by focusing and then clicking it). |was_edit| is true when the function diff --git a/chrome/browser/ui/autofill/autofill_dialog_controller_browsertest.cc b/chrome/browser/ui/autofill/autofill_dialog_controller_browsertest.cc index 8012838..73f8f92 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_controller_browsertest.cc +++ b/chrome/browser/ui/autofill/autofill_dialog_controller_browsertest.cc @@ -92,12 +92,13 @@ class TestAutofillDialogController : public AutofillDialogControllerImpl { } virtual bool InputIsValid(AutofillFieldType type, - const string16& value) OVERRIDE { + const string16& value) const OVERRIDE { return true; } virtual std::vector<AutofillFieldType> InputsAreValid( - const DetailOutputMap& inputs, ValidationType validation_type) OVERRIDE { + const DetailOutputMap& inputs, + ValidationType validation_type) const OVERRIDE { return std::vector<AutofillFieldType>(); } diff --git a/chrome/browser/ui/autofill/autofill_dialog_controller_impl.cc b/chrome/browser/ui/autofill/autofill_dialog_controller_impl.cc index b6ae652..c548cbc 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_controller_impl.cc +++ b/chrome/browser/ui/autofill/autofill_dialog_controller_impl.cc @@ -268,10 +268,12 @@ void AutofillDialogControllerImpl::Show() { const GURL& active_url = entry ? entry->GetURL() : contents_->GetURL(); invoked_from_same_origin_ = active_url.GetOrigin() == source_url_.GetOrigin(); - // Log any relevant security exceptions. + // Log any relevant UI metrics and security exceptions. + GetMetricLogger().LogDialogUiEvent( + dialog_type_, AutofillMetrics::DIALOG_UI_SHOWN); + GetMetricLogger().LogDialogSecurityMetric( - dialog_type_, - AutofillMetrics::SECURITY_METRIC_DIALOG_SHOWN); + dialog_type_, AutofillMetrics::SECURITY_METRIC_DIALOG_SHOWN); if (RequestingCreditCardInfo() && !TransmissionWillBeSecure()) { GetMetricLogger().LogDialogSecurityMetric( @@ -479,7 +481,7 @@ string16 AutofillDialogControllerImpl::SignInLinkText() const { } bool AutofillDialogControllerImpl::ShouldOfferToSaveInChrome() const { - return !IsPayingWithWallet(); + return !IsPayingWithWallet() && IsManuallyEditingAnySection(); } bool AutofillDialogControllerImpl::AutocheckoutIsRunning() const { @@ -851,6 +853,9 @@ void AutofillDialogControllerImpl::EditClickedForSection( model->FillInputs(inputs); section_editing_state_[section] = true; view_->UpdateSection(section, CLEAR_USER_INPUT); + + GetMetricLogger().LogDialogUiEvent( + dialog_type_, DialogSectionToUiEditEvent(section)); } void AutofillDialogControllerImpl::EditCancelledForSection( @@ -910,7 +915,7 @@ gfx::Image AutofillDialogControllerImpl::IconForField( } bool AutofillDialogControllerImpl::InputIsValid(AutofillFieldType type, - const string16& value) { + const string16& value) const { switch (type) { case EMAIL_ADDRESS: return IsValidEmailAddress(value); @@ -951,7 +956,7 @@ bool AutofillDialogControllerImpl::InputIsValid(AutofillFieldType type, } std::vector<AutofillFieldType> AutofillDialogControllerImpl::InputsAreValid( - const DetailOutputMap& inputs, ValidationType validation_type) { + const DetailOutputMap& inputs, ValidationType validation_type) const { std::vector<AutofillFieldType> invalid_fields; std::map<AutofillFieldType, string16> field_values; for (DetailOutputMap::const_iterator iter = inputs.begin(); @@ -1165,6 +1170,9 @@ void AutofillDialogControllerImpl::StartSignInFlow() { content::Source<content::NavigationController> source(view_->ShowSignIn()); registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, source); + + GetMetricLogger().LogDialogUiEvent( + dialog_type_, AutofillMetrics::DIALOG_UI_SIGNIN_SHOWN); } void AutofillDialogControllerImpl::EndSignInFlow() { @@ -1213,10 +1221,7 @@ void AutofillDialogControllerImpl::OnCancel() { if (callback_.is_null()) return; - GetMetricLogger().LogDialogUiDuration( - base::Time::Now() - dialog_shown_timestamp_, - dialog_type_, - AutofillMetrics::DIALOG_CANCELED); + LogOnCancelMetrics(); callback_.Run(NULL, std::string()); callback_ = base::Callback<void(const FormStructure*, const std::string&)>(); @@ -1328,6 +1333,8 @@ void AutofillDialogControllerImpl::SuggestionItemSelected( const SuggestionsMenuModel& model) { const DialogSection section = SectionForSuggestionsMenuModel(model); EditCancelledForSection(section); + + LogSuggestionItemSelectedMetric(model); } //////////////////////////////////////////////////////////////////////////////// @@ -1557,13 +1564,16 @@ AutofillDialogControllerImpl::AutofillDialogControllerImpl( const std::string&)>& callback) : profile_(Profile::FromBrowserContext(contents->GetBrowserContext())), contents_(contents), + initial_user_state_(AutofillMetrics::DIALOG_USER_STATE_UNKNOWN), + dialog_type_(dialog_type), form_structure_(form_structure, std::string()), invoked_from_same_origin_(true), source_url_(source_url), ssl_status_(form_structure.ssl_status), callback_(callback), ALLOW_THIS_IN_INITIALIZER_LIST( - account_chooser_model_(this, profile_->GetPrefs())), + account_chooser_model_(this, profile_->GetPrefs(), metric_logger_, + dialog_type)), ALLOW_THIS_IN_INITIALIZER_LIST( wallet_client_(profile_->GetRequestContext(), this)), ALLOW_THIS_IN_INITIALIZER_LIST(suggested_email_(this)), @@ -1573,8 +1583,6 @@ AutofillDialogControllerImpl::AutofillDialogControllerImpl( ALLOW_THIS_IN_INITIALIZER_LIST(suggested_shipping_(this)), input_showing_popup_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), - initial_user_state_(AutofillMetrics::DIALOG_USER_STATE_UNKNOWN), - dialog_type_(dialog_type), is_first_run_(!profile_->GetPrefs()->HasPrefPath( ::prefs::kAutofillDialogPayWithoutWallet)), is_submitting_(false), @@ -1801,6 +1809,14 @@ void AutofillDialogControllerImpl::SetCvcResult(const string16& cvc) { SuggestionsMenuModel* AutofillDialogControllerImpl:: SuggestionsMenuModelForSection(DialogSection section) { + const AutofillDialogControllerImpl* const_this = + static_cast<const AutofillDialogControllerImpl*>(this); + return const_cast<SuggestionsMenuModel*>( + const_this->SuggestionsMenuModelForSection(section)); +} + +const SuggestionsMenuModel* AutofillDialogControllerImpl:: + SuggestionsMenuModelForSection(DialogSection section) const { switch (section) { case SECTION_EMAIL: return &suggested_email_; @@ -1882,12 +1898,40 @@ void AutofillDialogControllerImpl::OnDidLoadRiskFingerprintData( } bool AutofillDialogControllerImpl::IsManuallyEditingSection( - DialogSection section) { - return section_editing_state_[section] || + DialogSection section) const { + std::map<DialogSection, bool>::const_iterator it = + section_editing_state_.find(section); + return (it != section_editing_state_.end() && it->second) || SuggestionsMenuModelForSection(section)-> GetItemKeyForCheckedItem().empty(); } +bool AutofillDialogControllerImpl::IsManuallyEditingAnySection() const { + for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { + if (IsManuallyEditingSection(static_cast<DialogSection>(section))) + return true; + } + return false; +} + +bool AutofillDialogControllerImpl::AllSectionsAreValid() const { + for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { + if (!SectionIsValid(static_cast<DialogSection>(section))) + return false; + } + return true; +} + +bool AutofillDialogControllerImpl::SectionIsValid( + DialogSection section) const { + if (!IsManuallyEditingSection(section)) + return true; + + DetailOutputMap detail_outputs; + view_->GetUserInput(SECTION_EMAIL, &detail_outputs); + return InputsAreValid(detail_outputs, VALIDATE_EDIT).empty(); +} + bool AutofillDialogControllerImpl::ShouldUseBillingForShipping() { // If the user is editing or inputting data, ask the view. if (IsManuallyEditingSection(SECTION_SHIPPING)) @@ -1902,7 +1946,9 @@ bool AutofillDialogControllerImpl::ShouldSaveDetailsLocally() { // It's possible that the user checked [X] Save details locally before // switching payment methods, so only ask the view whether to save details // locally if that checkbox is showing (currently if not paying with wallet). - return !IsPayingWithWallet() && view_->SaveDetailsLocally(); + // Also, if the user isn't editing any sections, there's no data to save + // locally. + return ShouldOfferToSaveInChrome() && view_->SaveDetailsLocally(); } void AutofillDialogControllerImpl::SetIsSubmitting(bool submitting) { @@ -2038,10 +2084,7 @@ void AutofillDialogControllerImpl::FinishSubmit() { wallet_items_->google_transaction_id()); callback_ = base::Callback<void(const FormStructure*, const std::string&)>(); - GetMetricLogger().LogDialogUiDuration( - base::Time::Now() - dialog_shown_timestamp_, - dialog_type_, - AutofillMetrics::DIALOG_ACCEPTED); + LogOnFinishSubmitMetrics(); switch (dialog_type_) { case DIALOG_TYPE_AUTOCHECKOUT: @@ -2060,6 +2103,65 @@ void AutofillDialogControllerImpl::FinishSubmit() { } } +void AutofillDialogControllerImpl::LogOnFinishSubmitMetrics() { + GetMetricLogger().LogDialogUiDuration( + base::Time::Now() - dialog_shown_timestamp_, + dialog_type_, + AutofillMetrics::DIALOG_ACCEPTED); + + GetMetricLogger().LogDialogUiEvent( + dialog_type_, AutofillMetrics::DIALOG_UI_ACCEPTED); + + AutofillMetrics::DialogDismissalState dismissal_state; + if (!IsManuallyEditingAnySection()) + dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_EXISTING_DATA; + else if (IsPayingWithWallet()) + dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_SAVE_TO_WALLET; + else if (ShouldSaveDetailsLocally()) + dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_SAVE_TO_AUTOFILL; + else + dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_NO_SAVE; + + GetMetricLogger().LogDialogDismissalState(dialog_type_, dismissal_state); +} + +void AutofillDialogControllerImpl::LogOnCancelMetrics() { + GetMetricLogger().LogDialogUiEvent( + dialog_type_, AutofillMetrics::DIALOG_UI_CANCELED); + + AutofillMetrics::DialogDismissalState dismissal_state; + if (!IsManuallyEditingAnySection()) + dismissal_state = AutofillMetrics::DIALOG_CANCELED_NO_EDITS; + else if (AllSectionsAreValid()) + dismissal_state = AutofillMetrics::DIALOG_CANCELED_NO_INVALID_FIELDS; + else + dismissal_state = AutofillMetrics::DIALOG_CANCELED_WITH_INVALID_FIELDS; + + GetMetricLogger().LogDialogDismissalState(dialog_type_, dismissal_state); + + GetMetricLogger().LogDialogUiDuration( + base::Time::Now() - dialog_shown_timestamp_, + dialog_type_, + AutofillMetrics::DIALOG_CANCELED); +} + +void AutofillDialogControllerImpl::LogSuggestionItemSelectedMetric( + const SuggestionsMenuModel& model) { + DialogSection section = SectionForSuggestionsMenuModel(model); + + AutofillMetrics::DialogUiEvent dialog_ui_event; + if (model.GetItemKeyForCheckedItem().empty()) { + // Selected to add a new item. + dialog_ui_event = DialogSectionToUiItemAddedEvent(section); + } else { + // Selected an existing item. + DCHECK(!section_editing_state_[section]); + dialog_ui_event = DialogSectionToUiSelectionChangedEvent(section); + } + + GetMetricLogger().LogDialogUiEvent(dialog_type_, dialog_ui_event); +} + AutofillMetrics::DialogInitialUserStateMetric AutofillDialogControllerImpl::GetInitialUserState() const { // Consider a user to be an Autofill user if the user has any credit cards diff --git a/chrome/browser/ui/autofill/autofill_dialog_controller_impl.h b/chrome/browser/ui/autofill/autofill_dialog_controller_impl.h index 92bd711..adfaccb 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_controller_impl.h +++ b/chrome/browser/ui/autofill/autofill_dialog_controller_impl.h @@ -124,10 +124,11 @@ class AutofillDialogControllerImpl : public AutofillDialogController, virtual void EditCancelledForSection(DialogSection section) OVERRIDE; virtual gfx::Image IconForField(AutofillFieldType type, const string16& user_input) const OVERRIDE; - virtual bool InputIsValid(AutofillFieldType type, const string16& value) - OVERRIDE; + virtual bool InputIsValid(AutofillFieldType type, + const string16& value) const OVERRIDE; virtual std::vector<AutofillFieldType> InputsAreValid( - const DetailOutputMap& inputs, ValidationType validation_type) OVERRIDE; + const DetailOutputMap& inputs, + ValidationType validation_type) const OVERRIDE; virtual void UserEditedOrActivatedInput(const DetailInput* input, gfx::NativeView parent_view, const gfx::Rect& content_bounds, @@ -311,6 +312,8 @@ class AutofillDialogControllerImpl : public AutofillDialogController, // Gets the SuggestionsMenuModel for |section|. SuggestionsMenuModel* SuggestionsMenuModelForSection(DialogSection section); + const SuggestionsMenuModel* SuggestionsMenuModelForSection( + DialogSection section) const; // And the reverse. DialogSection SectionForSuggestionsMenuModel( const SuggestionsMenuModel& model); @@ -353,7 +356,18 @@ class AutofillDialogControllerImpl : public AutofillDialogController, // Whether the user has chosen to enter all new data in |section|. This // happens via choosing "Add a new X..." from a section's suggestion menu. - bool IsManuallyEditingSection(DialogSection section); + bool IsManuallyEditingSection(DialogSection section) const; + + // Whether the user has chosen to enter all new data in at least one section. + bool IsManuallyEditingAnySection() const; + + // Whether all of the input fields currently showing in the dialog have valid + // contents. + bool AllSectionsAreValid() const; + + // Whether all of the input fields currently showing in the given |section| of + // the dialog have valid contents. + bool SectionIsValid(DialogSection section) const; // Whether the billing section should be used to fill in the shipping details. bool ShouldUseBillingForShipping(); @@ -381,6 +395,20 @@ class AutofillDialogControllerImpl : public AutofillDialogController, // in order to fill |form_structure_| and pass data back to the invoking page. void FinishSubmit(); + // Logs metrics when the dialog is submitted. + void LogOnFinishSubmitMetrics(); + + // Logs metrics when the dialog is canceled. + void LogOnCancelMetrics(); + + // Logs metrics when the edit ui is shown for the given |section|. + void LogEditUiShownMetric(DialogSection section); + + // Logs metrics when a suggestion item from the given |model| is selected. + void LogSuggestionItemSelectedMetric(const SuggestionsMenuModel& model); + + // Returns the metric corresponding to the user's initial state when + // interacting with this dialog. AutofillMetrics::DialogInitialUserStateMetric GetInitialUserState() const; // The |profile| for |contents_|. @@ -389,6 +417,15 @@ class AutofillDialogControllerImpl : public AutofillDialogController, // The WebContents where the Autofill action originated. content::WebContents* const contents_; + // For logging UMA metrics. + const AutofillMetrics metric_logger_; + base::Time dialog_shown_timestamp_; + base::Time autocheckout_started_timestamp_; + AutofillMetrics::DialogInitialUserStateMetric initial_user_state_; + + // Whether this is an Autocheckout or a requestAutocomplete dialog. + const DialogType dialog_type_; + FormStructure form_structure_; // Whether the URL visible to the user when this dialog was requested to be @@ -478,15 +515,6 @@ class AutofillDialogControllerImpl : public AutofillDialogController, base::WeakPtrFactory<AutofillDialogControllerImpl> weak_ptr_factory_; - // For logging UMA metrics. - const AutofillMetrics metric_logger_; - base::Time dialog_shown_timestamp_; - base::Time autocheckout_started_timestamp_; - AutofillMetrics::DialogInitialUserStateMetric initial_user_state_; - - // Whether this is an Autocheckout or a requestAutocomplete dialog. - const DialogType dialog_type_; - // Whether this is the first time this profile has seen the Autofill dialog. bool is_first_run_; diff --git a/chrome/browser/ui/autofill/autofill_dialog_models.cc b/chrome/browser/ui/autofill/autofill_dialog_models.cc index 37f358a..229fea3 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_models.cc +++ b/chrome/browser/ui/autofill/autofill_dialog_models.cc @@ -12,6 +12,7 @@ #include "base/utf_string_conversions.h" #include "chrome/common/pref_names.h" #include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_metrics.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" @@ -105,13 +106,17 @@ AccountChooserModelDelegate::~AccountChooserModelDelegate() {} AccountChooserModel::AccountChooserModel( AccountChooserModelDelegate* delegate, - PrefService* prefs) + PrefService* prefs, + const AutofillMetrics& metric_logger, + DialogType dialog_type) : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)), account_delegate_(delegate), checked_item_( prefs->GetBoolean(::prefs::kAutofillDialogPayWithoutWallet) ? kAutofillItemId : kWalletItemId), - had_wallet_error_(false) { + had_wallet_error_(false), + metric_logger_(metric_logger), + dialog_type_(dialog_type) { AddCheckItem(kWalletItemId, l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_GOOGLE_WALLET)); SetIcon( @@ -145,6 +150,20 @@ void AccountChooserModel::ExecuteCommand(int command_id, int event_flags) { if (checked_item_ == command_id) return; + // Log metrics. + AutofillMetrics::DialogUiEvent chooser_event; + if (command_id == kAutofillItemId) { + chooser_event = + AutofillMetrics::DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_TO_AUTOFILL; + } else if (checked_item_ == kAutofillItemId) { + chooser_event = + AutofillMetrics::DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_TO_WALLET; + } else { + chooser_event = + AutofillMetrics::DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_WALLET_ACCOUNT; + } + metric_logger_.LogDialogUiEvent(dialog_type_, chooser_event); + checked_item_ = command_id; account_delegate_->AccountChoiceChanged(); } diff --git a/chrome/browser/ui/autofill/autofill_dialog_models.h b/chrome/browser/ui/autofill/autofill_dialog_models.h index 2020e5d..44ff005 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_models.h +++ b/chrome/browser/ui/autofill/autofill_dialog_models.h @@ -11,9 +11,11 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/string16.h" +#include "components/autofill/browser/autofill_manager_delegate.h" #include "ui/base/models/combobox_model.h" #include "ui/base/models/simple_menu_model.h" +class AutofillMetrics; class PrefService; namespace autofill { @@ -110,7 +112,9 @@ class AccountChooserModel : public ui::SimpleMenuModel, public ui::SimpleMenuModel::Delegate { public: AccountChooserModel(AccountChooserModelDelegate* delegate, - PrefService* prefs); + PrefService* prefs, + const AutofillMetrics& metric_logger, + DialogType dialog_type); virtual ~AccountChooserModel(); // ui::SimpleMenuModel::Delegate implementation. @@ -148,6 +152,10 @@ class AccountChooserModel : public ui::SimpleMenuModel, // open. bool had_wallet_error_; + // For logging UMA metrics. + const AutofillMetrics& metric_logger_; + const DialogType dialog_type_; + DISALLOW_COPY_AND_ASSIGN(AccountChooserModel); }; diff --git a/chrome/browser/ui/autofill/autofill_dialog_models_unittest.cc b/chrome/browser/ui/autofill/autofill_dialog_models_unittest.cc index 36cdbcd..cd03020 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_models_unittest.cc +++ b/chrome/browser/ui/autofill/autofill_dialog_models_unittest.cc @@ -6,6 +6,7 @@ #include "chrome/browser/ui/autofill/autofill_dialog_models.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autofill_metrics.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -13,6 +14,19 @@ namespace autofill { namespace { +class TestAccountChooserModel : public AccountChooserModel { + public: + TestAccountChooserModel(AccountChooserModelDelegate* delegate, + PrefService* prefs, + const AutofillMetrics& metric_logger) + : AccountChooserModel(delegate, prefs, metric_logger, + DIALOG_TYPE_REQUEST_AUTOCOMPLETE) {} + virtual ~TestAccountChooserModel() {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestAccountChooserModel); +}; + class MockAccountChooserModelDelegate : public AccountChooserModelDelegate { public: MockAccountChooserModelDelegate() {} @@ -23,17 +37,20 @@ class MockAccountChooserModelDelegate : public AccountChooserModelDelegate { class AccountChooserModelTest : public testing::Test { public: - AccountChooserModelTest() : model_(&delegate_, profile_.GetPrefs()) {} + AccountChooserModelTest() + : model_(&delegate_, profile_.GetPrefs(), metric_logger_) {} virtual ~AccountChooserModelTest() {} Profile* profile() { return &profile_; } MockAccountChooserModelDelegate* delegate() { return &delegate_; } - AccountChooserModel* model() { return &model_; } + TestAccountChooserModel* model() { return &model_; } + const AutofillMetrics& metric_logger() { return metric_logger_; } private: TestingProfile profile_; MockAccountChooserModelDelegate delegate_; - AccountChooserModel model_; + TestAccountChooserModel model_; + AutofillMetrics metric_logger_; }; } // namespace @@ -43,14 +60,16 @@ TEST_F(AccountChooserModelTest, ObeysPref) { { profile()->GetPrefs()->SetBoolean( ::prefs::kAutofillDialogPayWithoutWallet, false); - AccountChooserModel model(delegate(), profile()->GetPrefs()); + TestAccountChooserModel model(delegate(), profile()->GetPrefs(), + metric_logger()); EXPECT_TRUE(model.WalletIsSelected()); } // When the user chose to "Pay without wallet", use Autofill. { profile()->GetPrefs()->SetBoolean( ::prefs::kAutofillDialogPayWithoutWallet, true); - AccountChooserModel model(delegate(), profile()->GetPrefs()); + TestAccountChooserModel model(delegate(), profile()->GetPrefs(), + metric_logger()); EXPECT_FALSE(model.WalletIsSelected()); } } diff --git a/chrome/browser/ui/autofill/autofill_dialog_types.cc b/chrome/browser/ui/autofill/autofill_dialog_types.cc index 81902c1..23cf0ba 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_types.cc +++ b/chrome/browser/ui/autofill/autofill_dialog_types.cc @@ -80,4 +80,73 @@ SuggestionState::SuggestionState(const string16& text, editable(editable) {} SuggestionState::~SuggestionState() {} +AutofillMetrics::DialogUiEvent DialogSectionToUiEditEvent( + DialogSection section) { + switch (section) { + case SECTION_EMAIL: + return AutofillMetrics::DIALOG_UI_EMAIL_EDIT_UI_SHOWN; + + case SECTION_BILLING: + return AutofillMetrics::DIALOG_UI_BILLING_EDIT_UI_SHOWN; + + case SECTION_CC_BILLING: + return AutofillMetrics::DIALOG_UI_CC_BILLING_EDIT_UI_SHOWN; + + case SECTION_SHIPPING: + return AutofillMetrics::DIALOG_UI_SHIPPING_EDIT_UI_SHOWN; + + case SECTION_CC: + return AutofillMetrics::DIALOG_UI_CC_EDIT_UI_SHOWN; + } + + NOTREACHED(); + return AutofillMetrics::NUM_DIALOG_UI_EVENTS; +} + +AutofillMetrics::DialogUiEvent DialogSectionToUiItemAddedEvent( + DialogSection section) { + switch (section) { + case SECTION_EMAIL: + return AutofillMetrics::DIALOG_UI_EMAIL_ITEM_ADDED; + + case SECTION_BILLING: + return AutofillMetrics::DIALOG_UI_BILLING_ITEM_ADDED; + + case SECTION_CC_BILLING: + return AutofillMetrics::DIALOG_UI_CC_BILLING_ITEM_ADDED; + + case SECTION_SHIPPING: + return AutofillMetrics::DIALOG_UI_SHIPPING_ITEM_ADDED; + + case SECTION_CC: + return AutofillMetrics::DIALOG_UI_CC_ITEM_ADDED; + } + + NOTREACHED(); + return AutofillMetrics::NUM_DIALOG_UI_EVENTS; +} + +AutofillMetrics::DialogUiEvent DialogSectionToUiSelectionChangedEvent( + DialogSection section) { + switch (section) { + case SECTION_EMAIL: + return AutofillMetrics::DIALOG_UI_EMAIL_SELECTED_SUGGESTION_CHANGED; + + case SECTION_BILLING: + return AutofillMetrics::DIALOG_UI_BILLING_SELECTED_SUGGESTION_CHANGED; + + case SECTION_CC_BILLING: + return AutofillMetrics::DIALOG_UI_CC_BILLING_SELECTED_SUGGESTION_CHANGED; + + case SECTION_SHIPPING: + return AutofillMetrics::DIALOG_UI_SHIPPING_SELECTED_SUGGESTION_CHANGED; + + case SECTION_CC: + return AutofillMetrics::DIALOG_UI_CC_SELECTED_SUGGESTION_CHANGED; + } + + NOTREACHED(); + return AutofillMetrics::NUM_DIALOG_UI_EVENTS; +} + } // namespace autofill diff --git a/chrome/browser/ui/autofill/autofill_dialog_types.h b/chrome/browser/ui/autofill/autofill_dialog_types.h index c10d3f6..15a770e 100644 --- a/chrome/browser/ui/autofill/autofill_dialog_types.h +++ b/chrome/browser/ui/autofill/autofill_dialog_types.h @@ -10,6 +10,7 @@ #include "base/callback_forward.h" #include "base/string16.h" +#include "components/autofill/browser/autofill_metrics.h" #include "components/autofill/browser/field_types.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/gfx/image/image.h" @@ -46,13 +47,19 @@ typedef base::Callback<bool(const DetailInput& input, // Sections of the dialog --- all fields that may be shown to the user fit under // one of these sections. enum DialogSection { - SECTION_EMAIL, + // Lower boundary value for looping over all sections. + SECTION_MIN, + + SECTION_EMAIL = SECTION_MIN, // The Autofill-backed dialog uses separate CC and billing sections. SECTION_CC, SECTION_BILLING, // The wallet-backed dialog uses a combined CC and billing section. SECTION_CC_BILLING, SECTION_SHIPPING, + + // Upper boundary value for looping over all sections. + SECTION_MAX = SECTION_SHIPPING }; // Used by UpdateSection() to indicate what to do with data that the user has @@ -140,6 +147,21 @@ struct SuggestionState { typedef std::vector<DetailInput> DetailInputs; typedef std::map<const DetailInput*, string16> DetailOutputMap; +// Returns the AutofillMetrics::DIALOG_UI_*_EDIT_UI_SHOWN metric corresponding +// to the |section|. +AutofillMetrics::DialogUiEvent DialogSectionToUiEditEvent( + DialogSection section); + +// Returns the AutofillMetrics::DIALOG_UI_*_ITEM_ADDED metric corresponding +// to the |section|. +AutofillMetrics::DialogUiEvent DialogSectionToUiItemAddedEvent( + DialogSection section); + +// Returns the AutofillMetrics::DIALOG_UI_*_ITEM_ADDED metric corresponding +// to the |section|. +AutofillMetrics::DialogUiEvent DialogSectionToUiSelectionChangedEvent( + DialogSection section); + } // namespace autofill #endif // CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_DIALOG_TYPES_H_ diff --git a/chrome/browser/ui/views/autofill/autofill_dialog_views.cc b/chrome/browser/ui/views/autofill/autofill_dialog_views.cc index fa85bcb..e8a0717 100644 --- a/chrome/browser/ui/views/autofill/autofill_dialog_views.cc +++ b/chrome/browser/ui/views/autofill/autofill_dialog_views.cc @@ -1211,7 +1211,7 @@ void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) { // Show or hide the "Save in chrome" checkbox. If nothing is in editing mode, // hide. If the controller tells us not to show it, likewise hide. save_in_chrome_checkbox_->SetVisible( - controller_->ShouldOfferToSaveInChrome() && AtLeastOneSectionIsEditing()); + controller_->ShouldOfferToSaveInChrome()); const bool has_suggestions = controller_->MenuModelForSection(group.section)->GetItemCount() > 0; @@ -1227,16 +1227,6 @@ void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) { ContentsPreferredSizeChanged(); } -bool AutofillDialogViews::AtLeastOneSectionIsEditing() { - for (DetailGroupMap::iterator iter = detail_groups_.begin(); - iter != detail_groups_.end(); ++iter) { - if (iter->second.manual_input && iter->second.manual_input->visible()) - return true; - } - - return false; -} - bool AutofillDialogViews::ValidateGroup( DetailsGroup* group, AutofillDialogController::ValidationType validation_type) { diff --git a/chrome/browser/ui/views/autofill/autofill_dialog_views.h b/chrome/browser/ui/views/autofill/autofill_dialog_views.h index 331ddc9..93e16ec 100644 --- a/chrome/browser/ui/views/autofill/autofill_dialog_views.h +++ b/chrome/browser/ui/views/autofill/autofill_dialog_views.h @@ -399,10 +399,6 @@ class AutofillDialogViews : public AutofillDialogView, // Updates the visual state of the given group as per the model. void UpdateDetailsGroupState(const DetailsGroup& group); - // Returns true if at least one of the details sections is in manual input - // mode. - bool AtLeastOneSectionIsEditing(); - // Gets a pointer to the DetailsGroup that's associated with the given section // of the dialog. DetailsGroup* GroupForSection(DialogSection section); diff --git a/components/autofill/browser/autofill_metrics.cc b/components/autofill/browser/autofill_metrics.cc index e83ae02..f36d3b1 100644 --- a/components/autofill/browser/autofill_metrics.cc +++ b/components/autofill/browser/autofill_metrics.cc @@ -353,6 +353,13 @@ void AutofillMetrics::LogCreditCardInfoBarMetric(InfoBarMetric metric) const { NUM_INFO_BAR_METRICS); } +void AutofillMetrics::LogDialogDismissalState( + autofill::DialogType dialog_type, + DialogDismissalState state) const { + std::string name = GetPrefixForDialogType(dialog_type) + ".DismissalState"; + LogUMAHistogramEnumeration(name, state, NUM_DIALOG_DISMISSAL_STATES); +} + void AutofillMetrics::LogDialogInitialUserState( autofill::DialogType dialog_type, DialogInitialUserStateMetric user_type) const { @@ -395,6 +402,12 @@ void AutofillMetrics::LogDialogUiDuration( LogUMAHistogramLongTimes(prefix + ".UiDuration." + suffix, duration); } +void AutofillMetrics::LogDialogUiEvent(autofill::DialogType dialog_type, + DialogUiEvent event) const { + std::string name = GetPrefixForDialogType(dialog_type) + ".UiEvents"; + LogUMAHistogramEnumeration(name, event, NUM_DIALOG_UI_EVENTS); +} + void AutofillMetrics::LogWalletErrorMetric(autofill::DialogType dialog_type, WalletErrorMetric metric) const { std::string name = GetPrefixForDialogType(dialog_type) + ".WalletErrors"; diff --git a/components/autofill/browser/autofill_metrics.h b/components/autofill/browser/autofill_metrics.h index ddd16b9..2f9abd0 100644 --- a/components/autofill/browser/autofill_metrics.h +++ b/components/autofill/browser/autofill_metrics.h @@ -67,12 +67,31 @@ class AutofillMetrics { // The action the user took to dismiss a dialog. enum DialogDismissalAction { - DIALOG_ACCEPTED = 0, // The user accepted, i.e. confirmed, the dialog. + DIALOG_ACCEPTED = 0, // The user accepted, i.e. submitted, the dialog. DIALOG_CANCELED, // The user canceled out of the dialog. }; - // The initial state of user that's interacting with a freshly shown - // requestAutocomplete or Autocheckout dialog. + // The state of the Autofill dialog when it was dismissed. + enum DialogDismissalState { + // The user submitted with no data available to save. + DIALOG_ACCEPTED_EXISTING_DATA, + // The saved details to Online Wallet on submit. + DIALOG_ACCEPTED_SAVE_TO_WALLET, + // The saved details to the local Autofill database on submit. + DIALOG_ACCEPTED_SAVE_TO_AUTOFILL, + // The user submitted without saving any edited sections. + DIALOG_ACCEPTED_NO_SAVE, + // The user canceled with no edit UI showing. + DIALOG_CANCELED_NO_EDITS, + // The user canceled with edit UI showing, but no invalid fields. + DIALOG_CANCELED_NO_INVALID_FIELDS, + // The user canceled with at least one invalid field. + DIALOG_CANCELED_WITH_INVALID_FIELDS, + NUM_DIALOG_DISMISSAL_STATES + }; + + // The initial state of user that's interacting with a freshly shown Autofill + // dialog. enum DialogInitialUserStateMetric { // Could not determine the user's state due to failure to communicate with // the Wallet server. @@ -115,6 +134,50 @@ class AutofillMetrics { NUM_DIALOG_SECURITY_METRICS }; + // For measuring how users are interacting with the Autofill dialog UI. + enum DialogUiEvent { + // Baseline metric: The dialog was shown. + DIALOG_UI_SHOWN = 0, + + // Dialog dismissal actions: + DIALOG_UI_ACCEPTED, + DIALOG_UI_CANCELED, + + // Selections within the account switcher: + // Switched from a Wallet account to local Autofill data. + DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_TO_AUTOFILL, + // Switched from local Autofill data to a Wallet account. + DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_TO_WALLET, + // Switched from one Wallet account to another one. + DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_WALLET_ACCOUNT, + + // The sign-in UI was shown. + DIALOG_UI_SIGNIN_SHOWN, + + // Selecting a different item from a suggestion menu dropdown: + DIALOG_UI_EMAIL_SELECTED_SUGGESTION_CHANGED, + DIALOG_UI_BILLING_SELECTED_SUGGESTION_CHANGED, + DIALOG_UI_CC_BILLING_SELECTED_SUGGESTION_CHANGED, + DIALOG_UI_SHIPPING_SELECTED_SUGGESTION_CHANGED, + DIALOG_UI_CC_SELECTED_SUGGESTION_CHANGED, + + // Showing the editing UI for a section of the dialog: + DIALOG_UI_EMAIL_EDIT_UI_SHOWN, + DIALOG_UI_BILLING_EDIT_UI_SHOWN, + DIALOG_UI_CC_BILLING_EDIT_UI_SHOWN, + DIALOG_UI_SHIPPING_EDIT_UI_SHOWN, + DIALOG_UI_CC_EDIT_UI_SHOWN, + + // Adding a new item in a section of the dialog: + DIALOG_UI_EMAIL_ITEM_ADDED, + DIALOG_UI_BILLING_ITEM_ADDED, + DIALOG_UI_CC_BILLING_ITEM_ADDED, + DIALOG_UI_SHIPPING_ITEM_ADDED, + DIALOG_UI_CC_ITEM_ADDED, + + NUM_DIALOG_UI_EVENTS + }; + enum InfoBarMetric { INFOBAR_SHOWN = 0, // We showed an infobar, e.g. prompting to save credit // card info. @@ -321,6 +384,10 @@ class AutofillMetrics { virtual void LogUserHappinessMetric(UserHappinessMetric metric) const; + // Logs |state| to the dismissal states histogram for |dialog_type|. + virtual void LogDialogDismissalState(autofill::DialogType dialog_type, + DialogDismissalState state) const; + // This should be called as soon as the user's signed-in status and Wallet // item count is known. Records that a user starting out in |user_state| is // interacting with a dialog of |dialog_type|. @@ -336,16 +403,20 @@ class AutofillMetrics { virtual void LogDialogSecurityMetric(autofill::DialogType dialog_type, DialogSecurityMetric metric) const; - // This should be called when the requestAutocomplete dialog, invoked by a - // dialog of type |dialog_type|, is closed. |duration| should be the time - // elapsed between the dialog being shown and it being closed. - // |dismissal_action| should indicate whether the user dismissed the dialog by - // submitting the form data or by canceling. + // This should be called when the Autofill dialog, invoked by a dialog of type + // |dialog_type|, is closed. |duration| should be the time elapsed between + // the dialog being shown and it being closed. |dismissal_action| should + // indicate whether the user dismissed the dialog by submitting the form data + // or by canceling. virtual void LogDialogUiDuration( const base::TimeDelta& duration, autofill::DialogType dialog_type, DialogDismissalAction dismissal_action) const; + // Logs |event| to the UI events histogram for |dialog_type|. + virtual void LogDialogUiEvent(autofill::DialogType dialog_type, + DialogUiEvent event) const; + // Logs |metric| to the Wallet errors histogram for |dialog_type|. virtual void LogWalletErrorMetric(autofill::DialogType dialog_type, WalletErrorMetric metric) const; diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 64c1e2d..06536b8 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -68,6 +68,11 @@ other types of suffix sets. </summary> </histogram> +<histogram name="Autocheckout.DismissalState" + enum="AutofillDialogDismissalState"> + <summary>The state of the Autocheckout dialog when it was dismissed.</summary> +</histogram> + <histogram name="Autocheckout.FlowDuration" units="ms"> <summary> Measures the time elapsed between when the user submitted the Autocheckout @@ -133,6 +138,12 @@ other types of suffix sets. </summary> </histogram> +<histogram name="Autocheckout.UiEvents" enum="AutofillDialogUiEvents"> + <summary> + Measures how users are interacting with the Autocheckout dialog UI. + </summary> +</histogram> + <histogram name="Autocheckout.WalletErrors" enum="WalletErrors"> <summary> Measures the frequency of errors in communicating with the Google Online @@ -2831,6 +2842,13 @@ other types of suffix sets. </summary> </histogram> +<histogram name="RequestAutocomplete.DismissalState" + enum="AutofillDialogDismissalState"> + <summary> + The state of the requestAutocomplete() dialog when it was dismissed. + </summary> +</histogram> + <histogram name="RequestAutocomplete.InitialUserState" enum="AutofillDialogInitialUserState"> <summary> @@ -2874,6 +2892,12 @@ other types of suffix sets. </summary> </histogram> +<histogram name="RequestAutocomplete.UiEvents" enum="AutofillDialogUiEvents"> + <summary> + Measures how users are interacting with the requestAutocomplete() dialog UI. + </summary> +</histogram> + <histogram name="RequestAutocomplete.WalletErrors" enum="WalletErrors"> <summary> Measures the frequency of errors in communicating with the Google Online @@ -3087,6 +3111,16 @@ other types of suffix sets. <int value="1" label="Includes type hints"/> </enum> +<enum name="AutofillDialogDismissalState" type="int"> + <int value="0" label="Submitted, existing data"/> + <int value="1" label="Submitted, saved to Wallet"/> + <int value="2" label="Submitted, saved locally"/> + <int value="3" label="Submitted, no save"/> + <int value="4" label="Canceled, no edits"/> + <int value="5" label="Canceled, no invalid fields"/> + <int value="6" label="Canceled, 1+ invalid fields"/> +</enum> + <enum name="AutofillDialogInitialUserState" type="int"> <int value="0" label="Not signed in, no Autofill"/> <int value="1" label="Not signed in, has Autofill"/> @@ -3107,6 +3141,31 @@ other types of suffix sets. <int value="2" label="Cross-origin frame"/> </enum> +<enum name="AutofillDialogUiEvents" type="int"> + <int value="0" label="Dialog shown"/> + <int value="1" label="Dialog submitted"/> + <int value="2" label="Dialog canceled"/> + <int value="3" label="Account switched: Wallet->Autofill"/> + <int value="4" label="Account switched: Autofill->Wallet"/> + <int value="5" label="Account switched: Wallet->Wallet"/> + <int value="6" label="Sign-in UI shown"/> + <int value="7" label="Selected different email suggestion"/> + <int value="8" label="Selected different billing suggestion"/> + <int value="9" label="Selected different cc+billing suggestion"/> + <int value="10" label="Selected different shipping suggestion"/> + <int value="11" label="Selected different cc suggestion"/> + <int value="12" label="Showed edit UI for email"/> + <int value="13" label="Showed edit UI for billing"/> + <int value="14" label="Showed edit UI for cc+billing"/> + <int value="15" label="Showed edit UI for shipping"/> + <int value="16" label="Showed edit UI for cc"/> + <int value="17" label="Selected 'Add email' suggestion"/> + <int value="18" label="Selected 'Add billing' suggestion"/> + <int value="19" label="Selected 'Add cc+billing' suggestion"/> + <int value="20" label="Selected 'Add shipping' suggestion"/> + <int value="21" label="Selected 'Add cc' suggestion"/> +</enum> + <enum name="AutofillExperimentId" type="int"> <int value="0" label="No Experiment"/> <int value="1" label="Unknown"/> |