Skip to content

Commit

Permalink
[iOS] New Feed Activity Buckets metric.
Browse files Browse the repository at this point in the history
Added a new metric to distribute users into feed activity buckets to be
able to better understand how users use the feed.

(cherry picked from commit 8eb7669)

Bug: 1446085
Change-Id: I8ab00931e09b0244297400f040e0bbdb8cb79917
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4507183
Auto-Submit: Guillem Perez <guiperez@google.com>
Commit-Queue: Justin DeWitt <dewittj@chromium.org>
Reviewed-by: Adam Arcaro <adamta@google.com>
Reviewed-by: Justin DeWitt <dewittj@chromium.org>
Cr-Original-Commit-Position: refs/heads/main@{#1148786}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4575801
Commit-Queue: Guillem Perez <guiperez@google.com>
Cr-Commit-Position: refs/branch-heads/5790@{#360}
Cr-Branched-From: 1d71a33-refs/heads/main@{#1148114}
  • Loading branch information
Guillem Perez authored and Chromium LUCI CQ committed Jun 5, 2023
1 parent 7d45bad commit 817dc15
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 0 deletions.
27 changes: 27 additions & 0 deletions ios/chrome/browser/ui/ntp/metrics/feed_metrics_constants.h
Expand Up @@ -31,6 +31,9 @@ extern const int kMinutesBetweenSessions;
// The max amount of cards in the Discover Feed.
extern const int kMaxCardsInFeed;

// The number of days for the Activity Buckets calculations.
extern const int kRangeForActivityBucketsInDays;

// Stores the time when the user visits an article on the feed.
extern NSString* const kArticleVisitTimestampKey;
// Stores the time elapsed on the feed when the user leaves.
Expand All @@ -47,6 +50,12 @@ extern NSString* const kLastInteractionTimeForFollowingGoodVisits;
extern NSString* const kLastDayTimeInFeedReportedKey;
// Stores the time spent on the feed for a day.
extern NSString* const kTimeSpentInFeedAggregateKey;
// Stores the last time the activity bucket was reported.
extern NSString* const kActivityBucketLastReportedDateKey;
// Stores the last 28 days of activity bucket reported days.
extern NSString* const kActivityBucketLastReportedDateArrayKey;
// Stores the latest activity bucket the user was on.
extern NSString* const kActivityBucketKey;

#pragma mark - Enums

Expand Down Expand Up @@ -208,6 +217,21 @@ enum class FeedSortType {
kMaxValue = kSortedByLatest,
};

// TODO(crbug.com/1447234): Clean up the kError enum.
// The values for the Feed Activity Buckets metric.
enum class FeedActivityBucket {
// No activity bucket for users active 0/28 days.
kNoActivity = 0,
// Low activity bucket for users active 1-7/28 days.
kLowActivity = 1,
// Medium activity bucket for users active 8-15/28 days.
kMediumActivity = 2,
// High activity bucket for users active 16+/28 days.
kHighActivity = 3,
// Highest enumerator. Recommended by Histogram metrics best practices.
kMaxValue = kHighActivity,
};

#pragma mark - Histograms

// Histogram name for the Time Spent in Feed.
Expand All @@ -223,6 +247,9 @@ extern const char kDiscoverFeedEngagementTypeHistogram[];
extern const char kFollowingFeedEngagementTypeHistogram[];
extern const char kAllFeedsEngagementTypeHistogram[];

// Histogram name for the feed activity bucket metric.
extern const char kAllFeedsActivityBucketsHistogram[];

// Histogram name for a Discover feed card shown at index.
extern const char kDiscoverFeedCardShownAtIndex[];

Expand Down
8 changes: 8 additions & 0 deletions ios/chrome/browser/ui/ntp/metrics/feed_metrics_constants.mm
Expand Up @@ -13,6 +13,7 @@
const int kNonShortClickSeconds = 10;
const int kMinutesBetweenSessions = 5;
const int kMaxCardsInFeed = 50;
const int kRangeForActivityBucketsInDays = 28;

NSString* const kArticleVisitTimestampKey = @"ShortClickInteractionTimestamp";
NSString* const kLongFeedVisitTimeAggregateKey =
Expand All @@ -30,6 +31,11 @@
@"LastInteractionTimeForGoodVisitsFollowing";
NSString* const kLastDayTimeInFeedReportedKey = @"LastDayTimeInFeedReported";
NSString* const kTimeSpentInFeedAggregateKey = @"TimeSpentInFeedAggregate";
NSString* const kActivityBucketLastReportedDateKey =
@"ActivityBucketLastReportedDate";
NSString* const kActivityBucketLastReportedDateArrayKey =
@"ActivityBucketLastReportedDateArray";
NSString* const kActivityBucketKey = @"FeedActivityBucket";

#pragma mark - Histograms

Expand All @@ -49,6 +55,8 @@
"NewTabPage.ContentSuggestions.Shown";
const char kFollowingFeedCardShownAtIndex[] =
"ContentSuggestions.Feed.WebFeed.Shown";
const char kAllFeedsActivityBucketsHistogram[] =
"ContentSuggestions.Feed.AllFeeds.Activity";
const char kDiscoverFeedNoticeCardFulfilled[] =
"ContentSuggestions.Feed.NoticeCardFulfilled2";
const char kDiscoverFeedArticlesFetchNetworkDurationSuccess[] =
Expand Down
119 changes: 119 additions & 0 deletions ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
Expand Up @@ -47,6 +47,10 @@ @interface FeedMetricsRecorder ()
@property(nonatomic, assign) BOOL goodVisitReportedDiscover;
@property(nonatomic, assign) BOOL goodVisitReportedFollowing;

// Tracking property to avoid duplicate recordings of the Activity Buckets
// metric.
@property(nonatomic, assign) NSDate* activityBucketLastReportedDate;

// Tracks whether user has engaged with the latest refreshed content. The term
// "engaged" is defined by its usage in this file. For example, it may be
// similar to `engagedSimpleReportedDiscover`.
Expand Down Expand Up @@ -194,6 +198,7 @@ - (void)recordNTPDidChangeVisibility:(BOOL)visible {
// Total time spent in feed metrics.
self.timeSpentInFeed =
base::Seconds([defaults doubleForKey:kTimeSpentInFeedAggregateKey]);
[self computeActivityBuckets];
[self recordTimeSpentInFeedIfDayIsDone];

self.previousTimeInFeedForGoodVisitSession =
Expand Down Expand Up @@ -887,6 +892,117 @@ - (void)recordDiscoverFeedUserActionHistogram:(FeedUserActionType)actionType
}
}

// Logs engagement daily for the Activity Buckets Calculation.
- (void)logDailyActivity {
NSDate* now = [NSDate date];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

// Check if the array is initialized.
NSMutableArray<NSDate*>* lastReportedArray = [[defaults
arrayForKey:kActivityBucketLastReportedDateArrayKey] mutableCopy];
if (!lastReportedArray) {
// Initialized before (could be empty).
lastReportedArray = [NSMutableArray new];
}

// Adds a daily entry to the `lastReportedArray` array
// only once when the user engages.
if ([now timeIntervalSinceDate:[lastReportedArray lastObject]] >=
(24 * 60 * 60) ||
lastReportedArray.count == 0) {
[lastReportedArray addObject:now];
[defaults setObject:lastReportedArray
forKey:kActivityBucketLastReportedDateArrayKey];
}
}

// Calculates the amount of dates the user has been active for the past 28 days.
- (void)computeActivityBuckets {
NSDate* now = [NSDate date];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

NSDate* lastActivityBucketReported = base::mac::ObjCCast<NSDate>(
[defaults objectForKey:kActivityBucketLastReportedDateKey]);
// If the `lastActivityBucketReported` does not exist, set it to now to
// prevent the first day from logging a metric.
if (!lastActivityBucketReported) {
lastActivityBucketReported = now;
[defaults setObject:lastActivityBucketReported
forKey:kActivityBucketLastReportedDateKey];
}

// Check if the last time the activity was reported is more than 24 hrs ago,
// and return for performance.
if ([now timeIntervalSinceDate:lastActivityBucketReported] < (24 * 60 * 60)) {
return;
}

// Retrieve activity bucket from storage.
FeedActivityBucket activityBucket =
(FeedActivityBucket)[defaults integerForKey:kActivityBucketKey];

// Calculate activity buckets.
// Check if the array is initialized.
NSMutableArray<NSDate*>* lastReportedArray = [[defaults
arrayForKey:kActivityBucketLastReportedDateArrayKey] mutableCopy];
if (!lastReportedArray) {
// Initialized before (could be empty).
lastReportedArray = [NSMutableArray new];
}

// Check for dates > 28 days and remove older items.
NSMutableIndexSet* toDelete = [[NSMutableIndexSet alloc] init];
for (NSUInteger i = 0; i < lastReportedArray.count; i++) {
if ([now timeIntervalSinceDate:[lastReportedArray objectAtIndex:i]] /
(24 * 60 * 60) >
kRangeForActivityBucketsInDays) {
[toDelete addIndex:i];
} else {
break;
}
}

// The count should never be < 1 for `lastReportedArray` when toDelete > 0 to
// prevent a crash / out of bounds errors.
if (toDelete.count > 0) {
CHECK(lastReportedArray.count >= 1);
[lastReportedArray removeObjectsAtIndexes:toDelete];
}
[defaults setObject:lastReportedArray
forKey:kActivityBucketLastReportedDateArrayKey];

// Check how many items in array.
NSUInteger datesActive = lastReportedArray.count;
switch (datesActive) {
case 0:
activityBucket = FeedActivityBucket::kNoActivity;
break;
case 1 ... 7:
activityBucket = FeedActivityBucket::kLowActivity;
break;
case 8 ... 15:
activityBucket = FeedActivityBucket::kMediumActivity;
break;
case 16 ... 28:
activityBucket = FeedActivityBucket::kHighActivity;
break;
default:
// This should never be reached, as dates should never be > 28 days.
CHECK(NO);
break;
}
[defaults setInteger:(int)activityBucket forKey:kActivityBucketKey];

// Activity Buckets Daily Run.
[self recordActivityBuckets:activityBucket];
[defaults setObject:now forKey:kActivityBucketLastReportedDateKey];
}

// Records the engagement buckets.
- (void)recordActivityBuckets:(FeedActivityBucket)activityBucket {
UMA_HISTOGRAM_ENUMERATION(kAllFeedsActivityBucketsHistogram, activityBucket);
}

// Records Feed engagement.
- (void)recordEngagement:(int)scrollDistance interacted:(BOOL)interacted {
scrollDistance = abs(scrollDistance);
Expand Down Expand Up @@ -1055,6 +1171,9 @@ - (void)recordEngaged {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:kEngagedWithFeedKey];

// Log engagement for Activity Buckets.
[self logDailyActivity];

UMA_HISTOGRAM_ENUMERATION(kAllFeedsEngagementTypeHistogram,
FeedEngagementType::kFeedEngaged);
}
Expand Down
15 changes: 15 additions & 0 deletions tools/metrics/histograms/enums.xml
Expand Up @@ -42897,6 +42897,21 @@ Called by update_permissions_policy_enum.py.-->
<int value="5" label="kDbCleanOutdatedDataError"/>
</enum>

<enum name="FeedActivityBucket">
<int value="0" label="No Activity">
The user has no activity in the last 28 days.
</int>
<int value="1" label="Low Activity">
The user has 1-7 days of activity in the last 28 days.
</int>
<int value="2" label="Medium Activity">
The user has 8-15 days of activity in the last 28 days.
</int>
<int value="3" label="High Activity">
The user has 16+ days of activity in the last 28 days.
</int>
</enum>

<enum name="FeedAutoplayEvent">
<obsolete>
Removed as of 05/2021. Replaced by FeedVideoPlayEvent.
Expand Down
18 changes: 18 additions & 0 deletions tools/metrics/histograms/metadata/content/histograms.xml
Expand Up @@ -1994,6 +1994,24 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>

<histogram name="ContentSuggestions.{FeedType}.Activity"
enum="FeedActivityBucket" expires_after="2023-11-24">
<owner>guiperez@google.com</owner>
<owner>feed@chromium.org</owner>
<summary>
Tracks user activity buckets with {FeedType}. Each bucket composed of the
user's activity level. Logs the for the first time 1 day after the metric is
active (only on first time use), then logs an activity bucket after at least
24hrs from the last log have elapsed. When the &quot;Engaged&quot; metric is
triggered we log one more day of activity during the past 28 days.
</summary>
<token key="FeedType">
<variant name="Feed" summary="the For-You feed"/>
<variant name="Feed.AllFeeds" summary="All Feeds combined"/>
<variant name="Feed.WebFeed" summary="the Following/Web Feed"/>
</token>
</histogram>

<histogram name="ContentSuggestions.{FeedType}.CardIndexOnSwitch" units="index"
expires_after="2023-04-21">
<owner>adamta@google.com</owner>
Expand Down

0 comments on commit 817dc15

Please sign in to comment.