diff --git a/browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md b/browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md index 452519d7090e..59c2fe4787aa 100644 --- a/browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md +++ b/browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md @@ -50,6 +50,8 @@ Please note that some targeting attributes require stricter controls on the tele * [activeNotifications](#activenotifications) * [isMajorUpgrade](#ismajorupgrade) * [hasActiveEnterprisePolicies](#hasactiveenterprisepolicies) +* [mainPingSubmissions](#mainpingsubmissions) +* [userMonthlyActivity](#usermonthlyactivity) ## Detailed usage @@ -816,3 +818,23 @@ A boolean. `true` if the browser just updated to a new major version. ### `hasActiveEnterprisePolicies` A boolean. `true` if any Enterprise Policies are active. + +### `mainPingSubmissions` + +Filter through the local telemetry pings archive submitted and select the `main` +pings sent at least 24 hours apart. Result is sorted in ascending order. + +```javascript +interface MainTelemetryPing { + id: string, + type: "main", + timestampCreated: number, +} + +declare const mainPingSubmissions: Promise +``` + +### `userMonthlyActivity` + +Returns an array of entries in the form `[int, unixTimestamp]` for each day of +user activity where the first entry is the total urls visited for that day. diff --git a/browser/components/newtab/lib/ASRouterTargeting.jsm b/browser/components/newtab/lib/ASRouterTargeting.jsm index 4d3d0cf20e0a..3d62bd141516 100644 --- a/browser/components/newtab/lib/ASRouterTargeting.jsm +++ b/browser/components/newtab/lib/ASRouterTargeting.jsm @@ -28,6 +28,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { HomePage: "resource:///modules/HomePage.jsm", AboutNewTab: "resource:///modules/AboutNewTab.jsm", BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm", + TelemetryArchive: "resource://gre/modules/TelemetryArchive.jsm", }); XPCOMUtils.defineLazyPreferenceGetter( @@ -243,6 +244,7 @@ const QueryCache = { CheckBrowserNeedsUpdate: new CheckBrowserNeedsUpdate(), RecentBookmarks: new CachedTargetingGetter("getRecentBookmarks"), ListAttachedOAuthClients: new CacheListAttachedOAuthClients(), + UserMonthlyActivity: new CachedTargetingGetter("getUserMonthlyActivity"), }, }; @@ -634,6 +636,38 @@ const TargetingGetters = { get hasActiveEnterprisePolicies() { return Services.policies.status === Services.policies.ACTIVE; }, + + get mainPingSubmissions() { + return ( + TelemetryArchive.promiseArchivedPingList() + // Filter out non-main pings. Do it before so we compare timestamps + // between pings of same type. + .then(pings => pings.filter(p => p.type === "main")) + .then(pings => { + if (pings.length <= 1) { + return pings; + } + // Pings are returned in ascending order. + return pings.reduce( + (acc, ping) => { + if ( + // Keep only main pings sent a day (or more) apart + new Date(ping.timestampCreated).toDateString() !== + new Date(acc[acc.length - 1].timestampCreated).toDateString() + ) { + acc.push(ping); + } + return acc; + }, + [pings[0]] + ); + }) + ); + }, + + get userMonthlyActivity() { + return QueryCache.queries.UserMonthlyActivity.get(); + }, }; this.ASRouterTargeting = { diff --git a/browser/components/newtab/test/browser/browser_asrouter_targeting.js b/browser/components/newtab/test/browser/browser_asrouter_targeting.js index a65c3a63feda..997d79b9d1da 100644 --- a/browser/components/newtab/test/browser/browser_asrouter_targeting.js +++ b/browser/components/newtab/test/browser/browser_asrouter_targeting.js @@ -1103,3 +1103,10 @@ add_task(async function check_is_major_upgrade() { "Should select the message" ); }); + +add_task(async function check_userMonthlyActivity() { + ok( + Array.isArray(await ASRouterTargeting.Environment.userMonthlyActivity), + "value is an array" + ); +}); diff --git a/browser/components/newtab/test/unit/asrouter/ASRouterTargeting.test.js b/browser/components/newtab/test/unit/asrouter/ASRouterTargeting.test.js index 360d2f61f052..dbdb72dc3954 100644 --- a/browser/components/newtab/test/unit/asrouter/ASRouterTargeting.test.js +++ b/browser/components/newtab/test/unit/asrouter/ASRouterTargeting.test.js @@ -273,6 +273,103 @@ describe("#CacheListAttachedOAuthClients", () => { assert.calledOnce(fxAccounts.listAttachedOAuthClients); }); }); +describe("#mainPingSubmissions", () => { + let promiseArchivedPingList; + let globals; + let sandbox; + beforeEach(() => { + sandbox = sinon.createSandbox(); + globals = new GlobalOverrider(); + }); + afterEach(() => { + sandbox.restore(); + globals.restore(); + }); + it("should return an empty list", async () => { + promiseArchivedPingList = sandbox.stub().resolves([]); + globals.set("TelemetryArchive", { promiseArchivedPingList }); + assert.typeOf( + await ASRouterTargeting.Environment.mainPingSubmissions, + "array", + "we get back an array" + ); + assert.lengthOf( + await ASRouterTargeting.Environment.mainPingSubmissions, + 0, + "no pings available" + ); + }); + it("should filter out bhr pings", async () => { + promiseArchivedPingList = sandbox.stub().resolves([ + { + id: "5c8c786b-eca5-734b-a755-7ec0f022aaaf", + timestampCreated: 1622525975674, + type: "bhr", + }, + ]); + globals.set("TelemetryArchive", { promiseArchivedPingList }); + assert.lengthOf( + await ASRouterTargeting.Environment.mainPingSubmissions, + 0, + "no `main` pings available" + ); + }); + it("should filter out pings less than 24hrs apart", async () => { + let startTime = 0; + promiseArchivedPingList = sandbox.stub().resolves([ + { + id: "5c8c786b-eca5-734b-a755-7ec0f022aaaf", + timestampCreated: 1622525975674, + type: "bhr", + }, + { + id: "5c8c786b-eca5-734b-a755-7ec0f022aaaa", + timestampCreated: startTime, + type: "main", + }, + { + id: "5c8c786b-eca5-734b-a755-7ec0f022aaaa", + timestampCreated: startTime + 1000, + type: "main", + }, + { + id: "5c8c786b-eca5-734b-a755-7ec0f022aaac", + timestampCreated: startTime + 86400001, + type: "main", + }, + ]); + globals.set("TelemetryArchive", { promiseArchivedPingList }); + assert.lengthOf( + await ASRouterTargeting.Environment.mainPingSubmissions, + 2, + "1 main ping is removed" + ); + }); + it("should allow for pings < 24hrs apart but on different days", async () => { + let startTime = new Date("2020-02-20").getTime(); + let oneDay = 86400000; + promiseArchivedPingList = sandbox.stub().resolves([ + { + id: "5c8c786b-eca5-734b-a755-7ec0f022aaaa", + // Using oneDay / 2 because timezone of browser running the test + // affects the calculation + timestampCreated: startTime - oneDay / 2, + type: "main", + }, + { + id: "5c8c786b-eca5-734b-a755-7ec0f022aaac", + timestampCreated: startTime + 1000, + type: "main", + }, + ]); + globals.set("TelemetryArchive", { promiseArchivedPingList }); + assert.lengthOf( + await ASRouterTargeting.Environment.mainPingSubmissions, + 2, + "pings are less day oneDay apart but fall on different days" + ); + }); +}); describe("ASRouterTargeting", () => { let evalStub; let sandbox; diff --git a/toolkit/modules/NewTabUtils.jsm b/toolkit/modules/NewTabUtils.jsm index 9347952efcda..2cd59513cb02 100644 --- a/toolkit/modules/NewTabUtils.jsm +++ b/toolkit/modules/NewTabUtils.jsm @@ -1341,6 +1341,22 @@ var ActivityStreamProvider = { return result; }, + /** + * Count the number of visited urls grouped by day + */ + getUserMonthlyActivity() { + let sqlQuery = ` + SELECT count(*), + strftime('%d-%m-%Y', visit_date/1000000.0, 'unixepoch') as date_format + FROM moz_historyvisits + WHERE visit_date > 0 + AND visit_date > strftime('%s','now','localtime','start of day','-30 days','utc') * 1000000 + GROUP BY date_format + `; + + return this.executePlacesQuery(sqlQuery); + }, + /** * Executes arbitrary query against places database *