blob: 46a0bbcd778afe269e3d91f697cbdaaa2dfe5563 [file] [log] [blame]
// Copyright 2018 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/webui/snippets_internals/snippets_internals_page_handler.h"
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/i18n/time_formatting.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/webui/snippets_internals/snippets_internals.mojom.h"
#include "chrome/common/pref_names.h"
#include "components/ntp_snippets/category_info.h"
#include "components/ntp_snippets/features.h"
#include "components/ntp_snippets/pref_names.h"
#include "components/ntp_snippets/remote/remote_suggestions_fetcher.h"
#include "components/ntp_snippets/remote/remote_suggestions_provider.h"
#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
#include "components/ntp_snippets/user_classifier.h"
#include "components/offline_pages/core/offline_page_feature.h"
#include "components/variations/variations_associated_data.h"
using ntp_snippets::Category;
using ntp_snippets::CategoryInfo;
using ntp_snippets::CategoryStatus;
using ntp_snippets::ContentSuggestion;
using ntp_snippets::RemoteSuggestionsProvider;
using ntp_snippets::RemoteSuggestionsFetcher;
using ntp_snippets::UserClassifier;
namespace {
/*
Non-instance helper functions.
*/
std::set<variations::VariationID> GetSnippetsExperiments() {
std::set<variations::VariationID> result;
for (const base::Feature* feature : ntp_snippets::GetAllFeatures()) {
base::FieldTrial* trial = base::FeatureList::GetFieldTrial(*feature);
if (!trial || trial->GetGroupNameWithoutActivation().empty())
continue;
for (variations::IDCollectionKey key :
{variations::GOOGLE_WEB_PROPERTIES,
variations::GOOGLE_WEB_PROPERTIES_SIGNED_IN,
variations::GOOGLE_WEB_PROPERTIES_TRIGGER}) {
const variations::VariationID id = variations::GetGoogleVariationID(
key, trial->trial_name(), trial->group_name());
if (id != variations::EMPTY_ID) {
result.insert(id);
}
}
}
return result;
}
std::string BooleanToString(bool value) {
return value ? "True" : "False";
}
std::string GetCategoryStatusName(CategoryStatus status) {
switch (status) {
case CategoryStatus::INITIALIZING:
return "INITIALIZING";
case CategoryStatus::AVAILABLE:
return "AVAILABLE";
case CategoryStatus::AVAILABLE_LOADING:
return "AVAILABLE_LOADING";
case CategoryStatus::NOT_PROVIDED:
return "NOT_PROVIDED";
case CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED:
return "ALL_SUGGESTIONS_EXPLICITLY_DISABLED";
case CategoryStatus::CATEGORY_EXPLICITLY_DISABLED:
return "CATEGORY_EXPLICITLY_DISABLED";
case CategoryStatus::LOADING_ERROR:
return "LOADING_ERROR";
}
return std::string();
}
snippets_internals::mojom::SuggestionItemPtr PrepareContentSuggestionItem(
const ContentSuggestion& suggestion,
int index) {
auto item = snippets_internals::mojom::SuggestionItem::New();
item->suggestionTitle = base::UTF16ToUTF8(suggestion.title());
item->suggestionIdWithinCategory = suggestion.id().id_within_category();
item->suggestionId = "content-suggestion-" + base::NumberToString(index);
item->url = suggestion.url().spec();
item->faviconUrl = suggestion.url_with_favicon().spec();
item->snippet = base::UTF16ToUTF8(suggestion.snippet_text());
item->publishDate =
base::UTF16ToUTF8(TimeFormatShortDateAndTime(suggestion.publish_date()));
item->publisherName = base::UTF16ToUTF8(suggestion.publisher_name());
item->score = suggestion.score();
return item;
}
} // namespace
// TODO: Add browser tests.
SnippetsInternalsPageHandler::SnippetsInternalsPageHandler(
mojo::PendingReceiver<snippets_internals::mojom::PageHandler> receiver,
mojo::PendingRemote<snippets_internals::mojom::Page> page,
ntp_snippets::ContentSuggestionsService* content_suggestions_service,
PrefService* pref_service)
: receiver_(this, std::move(receiver)),
content_suggestions_service_observer_(this),
content_suggestions_service_(content_suggestions_service),
remote_suggestions_provider_(
content_suggestions_service_
->remote_suggestions_provider_for_debugging()),
pref_service_(pref_service),
page_(std::move(page)) {}
SnippetsInternalsPageHandler::~SnippetsInternalsPageHandler() {}
/*
Observer methods.
*/
void SnippetsInternalsPageHandler::OnNewSuggestions(Category category) {
page_->OnSuggestionsChanged();
}
void SnippetsInternalsPageHandler::OnCategoryStatusChanged(
Category category,
CategoryStatus new_status) {
page_->OnSuggestionsChanged();
}
void SnippetsInternalsPageHandler::OnSuggestionInvalidated(
const ntp_snippets::ContentSuggestion::ID& suggestion_id) {
page_->OnSuggestionsChanged();
}
void SnippetsInternalsPageHandler::OnFullRefreshRequired() {
page_->OnSuggestionsChanged();
}
void SnippetsInternalsPageHandler::ContentSuggestionsServiceShutdown() {}
/*
Instance methods.
*/
void SnippetsInternalsPageHandler::GetGeneralProperties(
GetGeneralPropertiesCallback callback) {
auto properties = base::flat_map<std::string, std::string>();
properties["flag-article-suggestions"] = BooleanToString(
base::FeatureList::IsEnabled(ntp_snippets::kArticleSuggestionsFeature));
properties["flag-offlining-recent-pages-feature"] =
BooleanToString(base::FeatureList::IsEnabled(
offline_pages::kOffliningRecentPagesFeature));
if (remote_suggestions_provider_) {
const ntp_snippets::RemoteSuggestionsFetcher* fetcher =
remote_suggestions_provider_->suggestions_fetcher_for_debugging();
properties["switch-fetch-url"] = fetcher->GetFetchUrlForDebugging().spec();
}
std::set<variations::VariationID> ids = GetSnippetsExperiments();
std::vector<std::string> string_ids;
std::transform(
ids.begin(), ids.end(), std::back_inserter(string_ids),
[](variations::VariationID id) { return base::NumberToString(id); });
properties["experiment-ids"] = base::JoinString(string_ids, ", ");
std::move(callback).Run(properties);
}
void SnippetsInternalsPageHandler::GetUserClassifierProperties(
GetUserClassifierPropertiesCallback callback) {
auto properties = base::flat_map<std::string, std::string>();
properties["user-class"] = content_suggestions_service_->user_classifier()
->GetUserClassDescriptionForDebugging();
properties["avg-time-to-open-ntp"] = base::NumberToString(
content_suggestions_service_->user_classifier()->GetEstimatedAvgTime(
UserClassifier::Metric::NTP_OPENED));
properties["avg-time-to-show"] = base::NumberToString(
content_suggestions_service_->user_classifier()->GetEstimatedAvgTime(
UserClassifier::Metric::SUGGESTIONS_SHOWN));
properties["avg-time-to-use"] = base::NumberToString(
content_suggestions_service_->user_classifier()->GetEstimatedAvgTime(
UserClassifier::Metric::SUGGESTIONS_USED));
std::move(callback).Run(properties);
}
void SnippetsInternalsPageHandler::ClearUserClassifierProperties() {
content_suggestions_service_->user_classifier()
->ClearClassificationForDebugging();
}
void SnippetsInternalsPageHandler::GetCategoryRankerProperties(
GetCategoryRankerPropertiesCallback callback) {
auto properties = base::flat_map<std::string, std::string>();
std::vector<ntp_snippets::CategoryRanker::DebugDataItem> data =
content_suggestions_service_->category_ranker()->GetDebugData();
for (const auto& item : data) {
properties[item.label] = item.content;
}
std::move(callback).Run(properties);
}
void SnippetsInternalsPageHandler::ReloadSuggestions() {
if (remote_suggestions_provider_) {
remote_suggestions_provider_->ReloadSuggestions();
}
}
void SnippetsInternalsPageHandler::ClearCachedSuggestions() {
content_suggestions_service_->ClearAllCachedSuggestions();
page_->OnSuggestionsChanged();
}
void SnippetsInternalsPageHandler::GetRemoteContentSuggestionsProperties(
GetRemoteContentSuggestionsPropertiesCallback callback) {
auto properties = base::flat_map<std::string, std::string>();
if (remote_suggestions_provider_) {
const std::string& status =
remote_suggestions_provider_->suggestions_fetcher_for_debugging()
->GetLastStatusForDebugging();
if (!status.empty()) {
properties["remote-status"] = "Finished: " + status;
properties["remote-authenticated"] =
remote_suggestions_provider_->suggestions_fetcher_for_debugging()
->WasLastFetchAuthenticatedForDebugging()
? "Authenticated"
: "Non-authenticated";
}
}
base::Time time = base::Time::FromInternalValue(pref_service_->GetInt64(
ntp_snippets::prefs::kLastSuccessfulBackgroundFetchTime));
properties["last-background-fetch-time"] =
base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time));
std::move(callback).Run(properties);
}
void SnippetsInternalsPageHandler::FetchSuggestionsInBackground(
int64_t delaySeconds,
FetchSuggestionsInBackgroundCallback callback) {
DCHECK(delaySeconds >= 0);
suggestion_fetch_timer_.Start(
FROM_HERE, base::TimeDelta::FromSeconds(delaySeconds),
base::BindRepeating(
&SnippetsInternalsPageHandler::FetchSuggestionsInBackgroundImpl,
weak_ptr_factory_.GetWeakPtr(), base::Passed(std::move(callback))));
}
void SnippetsInternalsPageHandler::FetchSuggestionsInBackgroundImpl(
FetchSuggestionsInBackgroundCallback callback) {
remote_suggestions_provider_->RefetchInTheBackground(
RemoteSuggestionsProvider::FetchStatusCallback());
std::move(callback).Run();
}
void SnippetsInternalsPageHandler::GetLastJson(GetLastJsonCallback callback) {
std::string json = "";
if (remote_suggestions_provider_) {
const ntp_snippets::RemoteSuggestionsFetcher* fetcher =
remote_suggestions_provider_->suggestions_fetcher_for_debugging();
json = fetcher->GetLastJsonForDebugging();
}
std::move(callback).Run(json);
}
void SnippetsInternalsPageHandler::GetSuggestionsByCategory(
GetSuggestionsByCategoryCallback callback) {
CollectDismissedSuggestions(-1, std::move(callback),
std::vector<ContentSuggestion>());
}
void SnippetsInternalsPageHandler::GetSuggestionsByCategoryImpl(
GetSuggestionsByCategoryCallback callback) {
std::vector<snippets_internals::mojom::SuggestionCategoryPtr> categories;
int index = 0;
for (Category category : content_suggestions_service_->GetCategories()) {
CategoryStatus status =
content_suggestions_service_->GetCategoryStatus(category);
base::Optional<CategoryInfo> info =
content_suggestions_service_->GetCategoryInfo(category);
DCHECK(info);
const std::vector<ContentSuggestion>& suggestions =
content_suggestions_service_->GetSuggestionsForCategory(category);
std::vector<snippets_internals::mojom::SuggestionItemPtr> items;
for (const ContentSuggestion& suggestion : suggestions) {
snippets_internals::mojom::SuggestionItemPtr item =
PrepareContentSuggestionItem(suggestion, index++);
items.push_back(std::move(item));
}
std::vector<snippets_internals::mojom::SuggestionItemPtr> dismissed_items;
for (const ContentSuggestion& suggestion :
dismissed_suggestions_[category]) {
snippets_internals::mojom::SuggestionItemPtr item =
PrepareContentSuggestionItem(suggestion, index++);
dismissed_items.push_back(std::move(item));
}
auto suggestion_category =
snippets_internals::mojom::SuggestionCategory::New();
suggestion_category->categoryTitle = base::UTF16ToUTF8(info->title());
suggestion_category->status = GetCategoryStatusName(status);
suggestion_category->categoryId = category.id();
suggestion_category->suggestions = std::move(items);
suggestion_category->dismissedSuggestions = std::move(dismissed_items);
categories.push_back(std::move(suggestion_category));
}
std::move(callback).Run(std::move(categories));
}
void SnippetsInternalsPageHandler::ClearDismissedSuggestions(
int64_t category_id) {
Category category = Category::FromIDValue(category_id);
content_suggestions_service_->ClearDismissedSuggestionsForDebugging(category);
page_->OnSuggestionsChanged();
}
void SnippetsInternalsPageHandler::CollectDismissedSuggestions(
int last_index,
GetSuggestionsByCategoryCallback callback,
std::vector<ContentSuggestion> suggestions) {
std::vector<Category> categories =
content_suggestions_service_->GetCategories();
// Populate our last category results.
if (last_index > -1)
dismissed_suggestions_[categories[last_index]] = std::move(suggestions);
// Find the next category for this.
for (size_t i = 0; i < categories.size(); i++) {
// Continue the process in the next method call.
if (last_index + 1 >= 0 && (size_t)last_index + 1 == i) {
content_suggestions_service_->GetDismissedSuggestionsForDebugging(
categories[i],
base::BindRepeating(
&SnippetsInternalsPageHandler::CollectDismissedSuggestions,
weak_ptr_factory_.GetWeakPtr(), i,
base::Passed(std::move(callback))));
return;
}
}
// Call into impl once the dismissed categories have been collected.
GetSuggestionsByCategoryImpl(std::move(callback));
}