Commit ac2e0bbc authored by Dan Harrington's avatar Dan Harrington Committed by Commit Bot

Feed v2: Log UMA EngagementType

- Plumb actions from FeedStreamSurface over to MetricsReporter
- Port EngagementType reporting from FeedLoggingBridge to
  work with feed v2.

Bug: 1044139
Change-Id: Ia6478ddcfa25445c15a8874aad20bfccf9fcaad1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2137854
Commit-Queue: Dan H <harringtond@chromium.org>
Reviewed-by: default avatarCarlos Knippschild <carlosk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757665}
parent a5afc882
......@@ -138,6 +138,9 @@ public class FeedStreamSurface implements SurfaceActionsHandler, FeedActionsHand
void reportManageInterests(long nativeFeedStreamSurface, FeedStreamSurface caller);
// TODO(jianli): Call this function at the appropriate time.
void reportContextMenuOpened(long nativeFeedStreamSurface, FeedStreamSurface caller);
// TODO(jianli): Call this function at the appropriate time.
void reportStreamScrolled(
long nativeFeedStreamSurface, FeedStreamSurface caller, int distanceDp);
void loadMore(long nativeFeedStreamSurface, FeedStreamSurface caller);
void processThereAndBackAgain(
long nativeFeedStreamSurface, FeedStreamSurface caller, byte[] data);
......
......@@ -4,6 +4,8 @@
#include "chrome/browser/android/feed/v2/feed_stream_surface.h"
#include <vector>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "chrome/android/chrome_jni_headers/FeedStreamSurface_jni.h"
......@@ -93,37 +95,44 @@ void FeedStreamSurface::ReportNavigationStarted(
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& url,
jboolean in_new_tab) {
// TODO(harringtond): Implement this.
feed_stream_api_->ReportNavigationStarted();
}
void FeedStreamSurface::ReportNavigationDone(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& url,
jboolean in_new_tab) {
// TODO(harringtond): Implement this.
feed_stream_api_->ReportNavigationDone();
}
void FeedStreamSurface::ReportContentRemoved(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
// TODO(harringtond): Implement this.
feed_stream_api_->ReportContentRemoved();
}
void FeedStreamSurface::ReportNotInterestedIn(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
// TODO(harringtond): Implement this.
feed_stream_api_->ReportNotInterestedIn();
}
void FeedStreamSurface::ReportManageInterests(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
// TODO(harringtond): Implement this.
feed_stream_api_->ReportManageInterests();
}
void FeedStreamSurface::ReportContextMenuOpened(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
// TODO(harringtond): Implement this.
feed_stream_api_->ReportContextMenuOpened();
}
void FeedStreamSurface::ReportStreamScrolled(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
int distance_dp) {
feed_stream_api_->ReportStreamScrolled(distance_dp);
}
} // namespace feed
......@@ -85,6 +85,11 @@ class FeedStreamSurface : public FeedStreamApi::SurfaceInterface {
void ReportContextMenuOpened(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
// The user scrolled the feed by |distance_dp| and then stopped.
void ReportStreamScrolled(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
int distance_dp);
private:
base::android::ScopedJavaGlobalRef<jobject> java_ref_;
FeedStreamApi* feed_stream_api_;
......
......@@ -80,6 +80,7 @@ source_set("core_unit_tests") {
"feed_network_impl_unittest.cc",
"feed_store_unittest.cc",
"feed_stream_unittest.cc",
"metrics_reporter_unittest.cc",
"proto_util_unittest.cc",
"request_throttler_unittest.cc",
"stream_model_unittest.cc",
......
......@@ -19,6 +19,7 @@
#include "components/feed/core/v2/enums.h"
#include "components/feed/core/v2/feed_network.h"
#include "components/feed/core/v2/feed_store.h"
#include "components/feed/core/v2/metrics_reporter.h"
#include "components/feed/core/v2/refresh_task_scheduler.h"
#include "components/feed/core/v2/scheduling.h"
#include "components/feed/core/v2/stream_model.h"
......@@ -203,6 +204,7 @@ FeedStream::WireResponseTranslator::TranslateWireResponse(
FeedStream::FeedStream(RefreshTaskScheduler* refresh_task_scheduler,
EventObserver* stream_event_observer,
MetricsReporter* metrics_reporter,
Delegate* delegate,
PrefService* profile_prefs,
FeedNetwork* feed_network,
......@@ -212,6 +214,7 @@ FeedStream::FeedStream(RefreshTaskScheduler* refresh_task_scheduler,
const ChromeInfo& chrome_info)
: refresh_task_scheduler_(refresh_task_scheduler),
stream_event_observer_(stream_event_observer),
metrics_reporter_(metrics_reporter),
delegate_(delegate),
profile_prefs_(profile_prefs),
feed_network_(feed_network),
......@@ -460,4 +463,26 @@ void FeedStream::UnloadModel() {
model_.reset();
}
void FeedStream::ReportNavigationStarted() {
metrics_reporter_->NavigationStarted();
}
void FeedStream::ReportNavigationDone() {
metrics_reporter_->NavigationDone();
}
void FeedStream::ReportContentRemoved() {
metrics_reporter_->ContentRemoved();
}
void FeedStream::ReportNotInterestedIn() {
metrics_reporter_->NotInterestedIn();
}
void FeedStream::ReportManageInterests() {
metrics_reporter_->ManageInterests();
}
void FeedStream::ReportContextMenuOpened() {
metrics_reporter_->ContextMenuOpened();
}
void FeedStream::ReportStreamScrolled(int distance_dp) {
metrics_reporter_->StreamScrolled(distance_dp);
}
} // namespace feed
......@@ -31,10 +31,11 @@ class TickClock;
} // namespace base
namespace feed {
class FeedStore;
class StreamModel;
class FeedNetwork;
class FeedStore;
class MetricsReporter;
class RefreshTaskScheduler;
class StreamModel;
struct StreamModelUpdateRequest;
// Implements FeedStreamApi. |FeedStream| additionally exposes functionality
......@@ -77,6 +78,7 @@ class FeedStream : public FeedStreamApi,
FeedStream(RefreshTaskScheduler* refresh_task_scheduler,
EventObserver* stream_event_observer,
MetricsReporter* metrics_reporter,
Delegate* delegate,
PrefService* profile_prefs,
FeedNetwork* feed_network,
......@@ -105,6 +107,14 @@ class FeedStream : public FeedStreamApi,
bool CommitEphemeralChange(EphemeralChangeId id) override;
bool RejectEphemeralChange(EphemeralChangeId id) override;
void ReportNavigationStarted() override;
void ReportNavigationDone() override;
void ReportContentRemoved() override;
void ReportNotInterestedIn() override;
void ReportManageInterests() override;
void ReportContextMenuOpened() override;
void ReportStreamScrolled(int distance_dp) override;
// offline_pages::TaskQueue::Delegate.
void OnTaskQueueIsIdle() override;
......@@ -190,6 +200,7 @@ class FeedStream : public FeedStreamApi,
RefreshTaskScheduler* refresh_task_scheduler_;
EventObserver* stream_event_observer_;
MetricsReporter* metrics_reporter_;
Delegate* delegate_;
PrefService* profile_prefs_;
FeedNetwork* feed_network_;
......
......@@ -22,6 +22,7 @@
#include "components/feed/core/proto/v2/wire/request.pb.h"
#include "components/feed/core/shared_prefs/pref_names.h"
#include "components/feed/core/v2/feed_network.h"
#include "components/feed/core/v2/metrics_reporter.h"
#include "components/feed/core/v2/refresh_task_scheduler.h"
#include "components/feed/core/v2/scheduling.h"
#include "components/feed/core/v2/stream_model.h"
......@@ -296,9 +297,10 @@ class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
chrome_info.channel = version_info::Channel::STABLE;
chrome_info.version = base::Version({99, 1, 9911, 2});
stream_ = std::make_unique<FeedStream>(
&refresh_scheduler_, &event_observer_, this, &profile_prefs_, &network_,
store_.get(), task_environment_.GetMockClock(),
task_environment_.GetMockTickClock(), chrome_info);
&refresh_scheduler_, &event_observer_, &metrics_reporter_, this,
&profile_prefs_, &network_, store_.get(),
task_environment_.GetMockClock(), task_environment_.GetMockTickClock(),
chrome_info);
// Set the user classifier.
auto user_classifier = std::make_unique<TestUserClassifier>(
......@@ -351,6 +353,7 @@ class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
TestUserClassifier* user_classifier_;
TestEventObserver event_observer_;
MetricsReporter metrics_reporter_{task_environment_.GetMockTickClock()};
TestingPrefServiceSimple profile_prefs_;
TestFeedNetwork network_;
TestWireResponseTranslator response_translator_;
......
......@@ -3,10 +3,108 @@
// found in the LICENSE file.
#include "components/feed/core/v2/metrics_reporter.h"
#include <cmath>
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
namespace feed {
namespace {
using feed::internal::FeedEngagementType;
void ReportEngagementTypeHistogram(FeedEngagementType engagement_type) {
UMA_HISTOGRAM_ENUMERATION("ContentSuggestions.Feed.EngagementType",
engagement_type);
}
} // namespace
MetricsReporter::MetricsReporter(const base::TickClock* clock)
: clock_(clock) {}
MetricsReporter::~MetricsReporter() = default;
// Engagement Tracking.
void MetricsReporter::RecordInteraction() {
RecordEngagement(/*scroll_distance_dp=*/0, /*interacted=*/true);
ReportEngagementTypeHistogram(FeedEngagementType::kFeedInteracted);
}
void MetricsReporter::RecordEngagement(int scroll_distance_dp,
bool interacted) {
scroll_distance_dp = std::abs(scroll_distance_dp);
// Determine if this interaction is part of a new 'session'.
auto now = clock_->NowTicks();
const base::TimeDelta kVisitTimeout = base::TimeDelta::FromMinutes(5);
if (now - visit_start_time_ > kVisitTimeout) {
engaged_reported_ = false;
engaged_simple_reported_ = false;
}
// Reset the last active time for session measurement.
visit_start_time_ = now;
// Report the user as engaged-simple if they have scrolled any amount or
// interacted with the card, and we have not already reported it for this
// chrome run.
if (!engaged_simple_reported_ && (scroll_distance_dp > 0 || interacted)) {
ReportEngagementTypeHistogram(FeedEngagementType::kFeedEngagedSimple);
engaged_simple_reported_ = true;
}
// Report the user as engaged if they have scrolled more than the threshold or
// interacted with the card, and we have not already reported it this chrome
// run.
const int kMinScrollThresholdDp = 160; // 1 inch.
if (!engaged_reported_ &&
(scroll_distance_dp > kMinScrollThresholdDp || interacted)) {
ReportEngagementTypeHistogram(FeedEngagementType::kFeedEngaged);
engaged_reported_ = true;
}
}
void MetricsReporter::StreamScrolled(int distance_dp) {
RecordEngagement(distance_dp, /*interacted=*/false);
if (!scrolled_reported_) {
ReportEngagementTypeHistogram(FeedEngagementType::kFeedScrolled);
scrolled_reported_ = true;
}
}
void MetricsReporter::NavigationStarted() {
// TODO(harringtond): Add user actions.
// Report Feed_OpeningContent
RecordInteraction();
}
void MetricsReporter::NavigationDone() {
// TODO(harringtond): Use this or remove it.
}
void MetricsReporter::ContentRemoved() {
// TODO(harringtond): Add user actions.
// Report Feed_RemovedContent
RecordInteraction();
}
void MetricsReporter::NotInterestedIn() {
// TODO(harringtond): Add user actions.
// Report Feed_NotInterestedIn
RecordInteraction();
}
void MetricsReporter::ManageInterests() {
// TODO(harringtond): Add user actions.
// Report Feed_ManageInterests
RecordInteraction();
}
void MetricsReporter::ContextMenuOpened() {
// TODO(harringtond): Add user actions.
// Report Feed_OpenedContextMenu
}
void MetricsReporter::NetworkRequestComplete(NetworkRequestType type,
int http_status_code) {
......
......@@ -5,24 +5,71 @@
#ifndef COMPONENTS_FEED_CORE_V2_METRICS_REPORTER_H_
#define COMPONENTS_FEED_CORE_V2_METRICS_REPORTER_H_
#include "base/time/time.h"
#include "components/feed/core/v2/enums.h"
#include "components/feed/core/v2/feed_stream.h"
namespace base {
class TickClock;
} // namespace base
namespace feed {
namespace internal {
// This enum is used for a UMA histogram. Keep in sync with FeedEngagementType
// in enums.xml.
enum class FeedEngagementType {
kFeedEngaged = 0,
kFeedEngagedSimple = 1,
kFeedInteracted = 2,
kFeedScrolled = 3,
kMaxValue = FeedEngagementType::kFeedScrolled,
};
} // namespace internal
// Reports UMA metrics for feed.
class MetricsReporter : public FeedStream::EventObserver {
public:
explicit MetricsReporter(const base::TickClock* clock);
virtual ~MetricsReporter();
MetricsReporter(const MetricsReporter&) = delete;
MetricsReporter& operator=(const MetricsReporter&) = delete;
// User interactions.
void NavigationStarted();
void NavigationDone();
void ContentRemoved();
void NotInterestedIn();
void ManageInterests();
void ContextMenuOpened();
// Indicates the user scrolled the feed by |distance_dp| and then stopped
// scrolling.
void StreamScrolled(int distance_dp);
// Network metrics.
static void NetworkRequestComplete(NetworkRequestType type,
int http_status_code);
// FeedStream::EventObserver.
void OnLoadStream(LoadStreamStatus load_from_store_status,
LoadStreamStatus final_status) override;
void OnMaybeTriggerRefresh(TriggerType trigger,
bool clear_all_before_refresh) override;
void OnClearAll(base::TimeDelta time_since_last_clear) override;
private:
void RecordEngagement(int scroll_distance_dp, bool interacted);
void RecordInteraction();
const base::TickClock* clock_;
base::TimeTicks visit_start_time_;
bool engaged_simple_reported_ = false;
bool engaged_reported_ = false;
bool scrolled_reported_ = false;
};
} // namespace feed
......
// 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 "components/feed/core/v2/metrics_reporter.h"
#include <map>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_tick_clock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace feed {
using feed::internal::FeedEngagementType;
const base::TimeDelta kEpsilon = base::TimeDelta::FromMilliseconds(1);
class MetricsReporterTest : public testing::Test {
protected:
std::map<FeedEngagementType, int> ReportedEngagementType() {
std::map<FeedEngagementType, int> result;
for (const auto& bucket :
histogram_.GetAllSamples("ContentSuggestions.Feed.EngagementType")) {
result[static_cast<FeedEngagementType>(bucket.min)] += bucket.count;
}
return result;
}
base::SimpleTestTickClock clock_;
MetricsReporter reporter_{&clock_};
base::HistogramTester histogram_;
};
TEST_F(MetricsReporterTest, ScrollingSmall) {
reporter_.StreamScrolled(100);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedScrolled, 1},
{FeedEngagementType::kFeedEngagedSimple, 1},
});
EXPECT_EQ(want, ReportedEngagementType());
}
TEST_F(MetricsReporterTest, ScrollingCanTriggerEngaged) {
reporter_.StreamScrolled(161);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedScrolled, 1},
{FeedEngagementType::kFeedEngaged, 1},
{FeedEngagementType::kFeedEngagedSimple, 1},
});
EXPECT_EQ(want, ReportedEngagementType());
}
TEST_F(MetricsReporterTest, OpeningContentIsInteracting) {
reporter_.NavigationStarted();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
{FeedEngagementType::kFeedInteracted, 1},
{FeedEngagementType::kFeedEngagedSimple, 1},
});
EXPECT_EQ(want, ReportedEngagementType());
}
TEST_F(MetricsReporterTest, RemovingContentIsInteracting) {
reporter_.ContentRemoved();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
{FeedEngagementType::kFeedInteracted, 1},
{FeedEngagementType::kFeedEngagedSimple, 1},
});
EXPECT_EQ(want, ReportedEngagementType());
}
TEST_F(MetricsReporterTest, NotInterestedInIsInteracting) {
reporter_.NotInterestedIn();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
{FeedEngagementType::kFeedInteracted, 1},
{FeedEngagementType::kFeedEngagedSimple, 1},
});
EXPECT_EQ(want, ReportedEngagementType());
}
TEST_F(MetricsReporterTest, ManageInterestsInIsInteracting) {
reporter_.ManageInterests();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
{FeedEngagementType::kFeedInteracted, 1},
{FeedEngagementType::kFeedEngagedSimple, 1},
});
EXPECT_EQ(want, ReportedEngagementType());
}
TEST_F(MetricsReporterTest, VisitsCanLastMoreThanFiveMinutes) {
reporter_.StreamScrolled(1);
clock_.Advance(base::TimeDelta::FromMinutes(5) - kEpsilon);
reporter_.NavigationStarted();
clock_.Advance(base::TimeDelta::FromMinutes(5) - kEpsilon);
reporter_.StreamScrolled(1);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
{FeedEngagementType::kFeedInteracted, 1},
{FeedEngagementType::kFeedScrolled, 1},
{FeedEngagementType::kFeedEngagedSimple, 1},
});
EXPECT_EQ(want, ReportedEngagementType());
}
TEST_F(MetricsReporterTest, NewVisitAfterInactivity) {
reporter_.NavigationStarted();
reporter_.StreamScrolled(1);
clock_.Advance(base::TimeDelta::FromMinutes(5) + kEpsilon);
reporter_.NavigationStarted();
reporter_.StreamScrolled(1);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 2},
{FeedEngagementType::kFeedInteracted, 2},
{FeedEngagementType::kFeedEngagedSimple, 2},
{FeedEngagementType::kFeedScrolled, 1},
});
EXPECT_EQ(want, ReportedEngagementType());
}
} // namespace feed
......@@ -11,6 +11,7 @@
#include "components/feed/core/v2/feed_network_impl.h"
#include "components/feed/core/v2/feed_store.h"
#include "components/feed/core/v2/feed_stream.h"
#include "components/feed/core/v2/metrics_reporter.h"
#include "components/feed/core/v2/refresh_task_scheduler.h"
#include "net/base/network_change_notifier.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
......@@ -88,17 +89,18 @@ FeedService::FeedService(
refresh_task_scheduler_(std::move(refresh_task_scheduler)) {
stream_delegate_ = std::make_unique<StreamDelegateImpl>(local_state);
network_delegate_ = std::make_unique<NetworkDelegateImpl>(delegate_.get());
metrics_reporter_ =
std::make_unique<MetricsReporter>(base::DefaultTickClock::GetInstance());
feed_network_ = std::make_unique<FeedNetworkImpl>(
network_delegate_.get(), identity_manager, api_key, url_loader_factory,
base::DefaultTickClock::GetInstance(), profile_prefs);
store_ = std::make_unique<FeedStore>(std::move(database));
stream_ = std::make_unique<FeedStream>(
refresh_task_scheduler_.get(),
nullptr, // TODO(harringtond): Implement EventObserver.
stream_delegate_.get(), profile_prefs, feed_network_.get(), store_.get(),
base::DefaultClock::GetInstance(), base::DefaultTickClock::GetInstance(),
chrome_info);
refresh_task_scheduler_.get(), metrics_reporter_.get(),
metrics_reporter_.get(), stream_delegate_.get(), profile_prefs,
feed_network_.get(), store_.get(), base::DefaultClock::GetInstance(),
base::DefaultTickClock::GetInstance(), chrome_info);
stream_delegate_->Initialize(static_cast<FeedStream*>(stream_.get()));
......
......@@ -31,6 +31,7 @@ class IdentityManager;
namespace feed {
class RefreshTaskScheduler;
class MetricsReporter;
class FeedNetwork;
class FeedStore;
......@@ -74,6 +75,7 @@ class FeedService : public KeyedService {
// be null if |FeedStreamApi| is created externally.
std::unique_ptr<Delegate> delegate_;
std::unique_ptr<StreamDelegateImpl> stream_delegate_;
std::unique_ptr<MetricsReporter> metrics_reporter_;
std::unique_ptr<NetworkDelegateImpl> network_delegate_;
std::unique_ptr<FeedNetwork> feed_network_;
std::unique_ptr<FeedStore> store_;
......
......@@ -52,6 +52,16 @@ class FeedStreamApi {
virtual bool CommitEphemeralChange(EphemeralChangeId id) = 0;
// Rejects a change. Returns false if the change does not exist.
virtual bool RejectEphemeralChange(EphemeralChangeId id) = 0;
// User interaction reporting. These should have no side-effects other than
// reporting metrics.
virtual void ReportNavigationStarted() = 0;
virtual void ReportNavigationDone() = 0;
virtual void ReportContentRemoved() = 0;
virtual void ReportNotInterestedIn() = 0;
virtual void ReportManageInterests() = 0;
virtual void ReportContextMenuOpened() = 0;
virtual void ReportStreamScrolled(int distance_dp) = 0;
};
} // namespace feed
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment