blob: 3b144d6a3b93039823d58b07781cfcdfb4eb7b6f [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/search/search_metrics_observer.h"
#include "base/containers/flat_set.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
#include "chrome/browser/ui/app_list/search/ranking/types.h"
#include "chrome/browser/ui/app_list/search/search_controller.h"
#include "components/drive/drive_pref_names.h"
#include "components/prefs/pref_service.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace app_list {
namespace {
using Result = ash::AppListNotifier::Result;
using Location = ash::AppListNotifier::Location;
// Represents possible error states of the metrics observer itself. These values
// persist to logs. Entries should not be renumbered and numeric values should
// never be reused.
enum class Error {
kMissingNotifier = 0,
kResultNotFound = 1,
kUntrackedLocation = 2,
kUntypedResult = 3,
kMaxValue = kUntypedResult
};
// Represents the actions a user can take in the launcher. These values persist
// to logs. Entries should not be renumbered and numeric values should never be
// reused.
enum class Action {
kImpression = 0,
kLaunch = 1,
kAbandon = 2,
kIgnore = 3,
kMaxValue = kIgnore
};
char kHistogramPrefix[] = "Apps.AppList.Search.";
void LogError(Error error) {
base::UmaHistogramEnumeration(base::StrCat({kHistogramPrefix, "Error"}),
error);
}
std::string GetViewString(Location location, const std::u16string& raw_query) {
std::u16string query;
base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
switch (location) {
case Location::kList:
return query.empty() ? "ListZeroState" : "ListSearch";
case Location::kTile:
return query.empty() ? "AppsZeroState" : "AppsSearch";
case Location::kAnswerCard:
return "AnswerCard";
case Location::kChip:
return "Chip";
case Location::kRecentApps:
return "RecentApps";
case Location::kContinue:
return "Continue";
default:
LogError(Error::kUntrackedLocation);
return "Untracked";
}
}
std::set<ash::SearchResultType> TypeSet(const std::vector<Result>& results) {
std::set<ash::SearchResultType> types;
for (const auto& result : results) {
types.insert(result.type);
}
return types;
}
// Log an action on a set of search result types.
void LogTypeActions(const std::string& action_name,
Location location,
const std::u16string& query,
const std::set<ash::SearchResultType>& types) {
const std::string histogram_name = base::StrCat(
{kHistogramPrefix, GetViewString(location, query), ".", action_name});
for (auto type : types) {
if (type == ash::SEARCH_RESULT_TYPE_BOUNDARY) {
LogError(Error::kUntypedResult);
} else {
base::UmaHistogramEnumeration(histogram_name, type,
ash::SEARCH_RESULT_TYPE_BOUNDARY);
}
}
}
// Log an action for a search result view as a whole.
void LogViewAction(Location location,
const std::u16string& query,
Action action) {
const std::string histogram_name =
base::StrCat({kHistogramPrefix, GetViewString(location, query)});
base::UmaHistogramEnumeration(histogram_name, action);
}
void LogIsDriveEnabled(Profile* profile) {
// Logs false for disabled and true for enabled, corresponding to the
// BooleanEnabled enum.
base::UmaHistogramBoolean(
"Apps.AppList.ContinueIsDriveEnabled",
!profile->GetPrefs()->GetBoolean(drive::prefs::kDisableDrive));
}
void LogContinueMetrics(const std::vector<Result>& results) {
int drive_count = 0;
int local_count = 0;
int help_app_count = 0;
for (const auto& result : results) {
switch (result.type) {
case ash::SearchResultType::ZERO_STATE_DRIVE:
++drive_count;
break;
case ash::SearchResultType::ZERO_STATE_FILE:
++local_count;
break;
case ash::SearchResultType::HELP_APP_UPDATES:
++help_app_count;
break;
default:
NOTREACHED() << static_cast<int>(result.type);
}
}
base::UmaHistogramExactLinear("Apps.AppList.Search.ContinueResultCount.Total",
results.size(), 10);
base::UmaHistogramExactLinear("Apps.AppList.Search.ContinueResultCount.Drive",
drive_count, 10);
base::UmaHistogramExactLinear("Apps.AppList.Search.ContinueResultCount.Local",
local_count, 10);
base::UmaHistogramExactLinear(
"Apps.AppList.Search.ContinueResultCount.HelpApp", help_app_count, 10);
base::UmaHistogramBoolean("Apps.AppList.Search.DriveContinueResultsShown",
drive_count > 0);
}
} // namespace
SearchMetricsObserver::SearchMetricsObserver(Profile* profile,
ash::AppListNotifier* notifier) {
if (notifier) {
observation_.Observe(notifier);
} else {
LogError(Error::kMissingNotifier);
}
if (profile)
LogIsDriveEnabled(profile);
}
SearchMetricsObserver::~SearchMetricsObserver() = default;
void SearchMetricsObserver::OnImpression(Location location,
const std::vector<Result>& results,
const std::u16string& query) {
LogTypeActions("Impression", location, query, TypeSet(results));
if (!results.empty())
LogViewAction(location, query, Action::kImpression);
if (location == Location::kContinue)
LogContinueMetrics(results);
}
void SearchMetricsObserver::OnAbandon(Location location,
const std::vector<Result>& results,
const std::u16string& query) {
LogTypeActions("Abandon", location, query, TypeSet(results));
if (!results.empty())
LogViewAction(location, query, Action::kAbandon);
}
void SearchMetricsObserver::OnLaunch(Location location,
const Result& launched,
const std::vector<Result>& shown,
const std::u16string& query) {
LogViewAction(location, query, Action::kLaunch);
// Record an ignore for all result types in this view. If other views are
// shown, they are handled by OnIgnore.
std::set<ash::SearchResultType> types;
for (const auto& result : shown) {
if (result.type != launched.type) {
types.insert(result.type);
}
}
LogTypeActions("Ignore", location, query, types);
LogTypeActions("Launch", location, query, {launched.type});
// Record the launch index.
int launched_index = -1;
for (size_t i = 0; i < shown.size(); ++i) {
if (shown[i].id == launched.id) {
launched_index = base::checked_cast<int>(i);
break;
}
}
const std::string histogram_name = base::StrCat(
{kHistogramPrefix, GetViewString(location, query), ".LaunchIndex"});
base::UmaHistogramExactLinear(histogram_name, launched_index, 50);
}
void SearchMetricsObserver::OnIgnore(Location location,
const std::vector<Result>& results,
const std::u16string& query) {
// We have no two concurrently displayed views showing the same result types,
// so it's safe to log an ignore for all result types here.
LogTypeActions("Ignore", location, query, TypeSet(results));
if (!results.empty())
LogViewAction(location, query, Action::kIgnore);
}
} // namespace app_list