зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1598724 - Add event ping migration, DE layout with DS, and campaign to flight id value migration to New Tab Page r=Mardak
Differential Revision: https://phabricator.services.mozilla.com/D54348 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
116ada0728
Коммит
02c08eadfc
|
@ -26,7 +26,7 @@ const ALLOWED_CSS_URL_PREFIXES = [
|
|||
"https://img-getpocket.cdn.mozilla.net/",
|
||||
];
|
||||
const DUMMY_CSS_SELECTOR = "DUMMY#CSS.SELECTOR";
|
||||
let rickRollCache = []; // Cache of random probability values for a spoc position
|
||||
let rollCache = []; // Cache of random probability values for a spoc position
|
||||
|
||||
/**
|
||||
* Validate a CSS declaration. The values are assumed to be normalized by CSSOM.
|
||||
|
@ -154,7 +154,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
|
|||
url,
|
||||
context,
|
||||
cta,
|
||||
campaign_id,
|
||||
flight_id,
|
||||
id,
|
||||
shim,
|
||||
} = spoc;
|
||||
|
@ -179,7 +179,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
|
|||
cta_text={cta}
|
||||
cta_url={url}
|
||||
subtitle={context}
|
||||
campaignId={campaign_id}
|
||||
flightId={flight_id}
|
||||
id={id}
|
||||
pos={0}
|
||||
shim={shim}
|
||||
|
@ -264,17 +264,18 @@ export class _DiscoveryStreamBase extends React.PureComponent {
|
|||
|
||||
componentWillReceiveProps(oldProps) {
|
||||
if (this.props.DiscoveryStream.layout !== oldProps.DiscoveryStream.layout) {
|
||||
rickRollCache = [];
|
||||
rollCache = [];
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// Select layout render data by adding spocs and position to recommendations
|
||||
const { layoutRender, spocsFill } = selectLayoutRender(
|
||||
this.props.DiscoveryStream,
|
||||
this.props.Prefs.values,
|
||||
rickRollCache
|
||||
);
|
||||
const { layoutRender, spocsFill } = selectLayoutRender({
|
||||
state: this.props.DiscoveryStream,
|
||||
prefs: this.props.Prefs.values,
|
||||
rollCache,
|
||||
lang: this.props.document.documentElement.lang,
|
||||
});
|
||||
const { config, spocs, feeds } = this.props.DiscoveryStream;
|
||||
|
||||
// Send SPOCS Fill if any. Note that it should not send it again if the same
|
||||
|
@ -412,4 +413,5 @@ export const DiscoveryStreamBase = connect(state => ({
|
|||
DiscoveryStream: state.DiscoveryStream,
|
||||
Prefs: state.Prefs,
|
||||
Sections: state.Sections,
|
||||
document: global.document,
|
||||
}))(_DiscoveryStreamBase);
|
||||
|
|
|
@ -20,7 +20,7 @@ export class CardGrid extends React.PureComponent {
|
|||
<DSCard
|
||||
key={`dscard-${rec.id}`}
|
||||
pos={rec.pos}
|
||||
campaignId={rec.campaign_id}
|
||||
flightId={rec.flight_id}
|
||||
image_src={rec.image_src}
|
||||
raw_image_src={rec.raw_image_src}
|
||||
title={rec.title}
|
||||
|
|
|
@ -213,7 +213,7 @@ export class DSCard extends React.PureComponent {
|
|||
/>
|
||||
)}
|
||||
<ImpressionStats
|
||||
campaignId={this.props.campaignId}
|
||||
flightId={this.props.flightId}
|
||||
rows={[
|
||||
{
|
||||
id: this.props.id,
|
||||
|
@ -238,7 +238,7 @@ export class DSCard extends React.PureComponent {
|
|||
pocket_id={this.props.pocket_id}
|
||||
shim={this.props.shim}
|
||||
bookmarkGuid={this.props.bookmarkGuid}
|
||||
campaignId={this.props.campaignId}
|
||||
flightId={this.props.flightId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -47,7 +47,7 @@ export class DSLinkMenu extends React.PureComponent {
|
|||
"OpenInPrivateWindow",
|
||||
"Separator",
|
||||
"BlockUrl",
|
||||
...(this.props.campaignId ? ["ShowPrivacyInfo"] : []),
|
||||
...(this.props.flightId ? ["ShowPrivacyInfo"] : []),
|
||||
];
|
||||
const type = this.props.type || "DISCOVERY_STREAM";
|
||||
const title = this.props.title || this.props.source;
|
||||
|
@ -76,7 +76,7 @@ export class DSLinkMenu extends React.PureComponent {
|
|||
pocket_id: this.props.pocket_id,
|
||||
shim: this.props.shim,
|
||||
bookmarkGuid: this.props.bookmarkGuid,
|
||||
campaign_id: this.props.campaignId,
|
||||
flight_id: this.props.flightId,
|
||||
}}
|
||||
/>
|
||||
</ContextMenuButton>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import React from "react";
|
||||
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
|
||||
import { FluentOrText } from "content-src/components/FluentOrText/FluentOrText";
|
||||
|
||||
export class DSMessage extends React.PureComponent {
|
||||
render() {
|
||||
|
@ -17,11 +18,13 @@ export class DSMessage extends React.PureComponent {
|
|||
/>
|
||||
)}
|
||||
{this.props.title && (
|
||||
<span className="title-text">{this.props.title}</span>
|
||||
<span className="title-text">
|
||||
<FluentOrText message={this.props.title} />
|
||||
</span>
|
||||
)}
|
||||
{this.props.link_text && this.props.link_url && (
|
||||
<SafeAnchor className="link" url={this.props.link_url}>
|
||||
{this.props.link_text}
|
||||
<FluentOrText message={this.props.link_text} />
|
||||
</SafeAnchor>
|
||||
)}
|
||||
</header>
|
||||
|
|
|
@ -65,7 +65,7 @@ export class DSTextPromo extends React.PureComponent {
|
|||
<p className="subtitle">{this.props.subtitle}</p>
|
||||
</div>
|
||||
<ImpressionStats
|
||||
campaignId={this.props.campaignId}
|
||||
flightId={this.props.flightId}
|
||||
rows={[
|
||||
{
|
||||
id: this.props.id,
|
||||
|
|
|
@ -62,7 +62,7 @@ export class Hero extends React.PureComponent {
|
|||
<PlaceholderDSCard key={`dscard-${index}`} />
|
||||
) : (
|
||||
<DSCard
|
||||
campaignId={rec.campaign_id}
|
||||
flightId={rec.flight_id}
|
||||
key={`dscard-${rec.id}`}
|
||||
image_src={rec.image_src}
|
||||
raw_image_src={rec.raw_image_src}
|
||||
|
@ -117,7 +117,7 @@ export class Hero extends React.PureComponent {
|
|||
/>
|
||||
</div>
|
||||
<ImpressionStats
|
||||
campaignId={heroRec.campaign_id}
|
||||
flightId={heroRec.flight_id}
|
||||
rows={[
|
||||
{
|
||||
id: heroRec.id,
|
||||
|
@ -142,7 +142,7 @@ export class Hero extends React.PureComponent {
|
|||
pocket_id={heroRec.pocket_id}
|
||||
shim={heroRec.shim}
|
||||
bookmarkGuid={heroRec.bookmarkGuid}
|
||||
campaignId={heroRec.campaign_id}
|
||||
flightId={heroRec.flight_id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -90,7 +90,7 @@ export class ListItem extends React.PureComponent {
|
|||
rawSource={this.props.raw_image_src}
|
||||
/>
|
||||
<ImpressionStats
|
||||
campaignId={this.props.campaignId}
|
||||
flightId={this.props.flightId}
|
||||
rows={[
|
||||
{
|
||||
id: this.props.id,
|
||||
|
@ -116,7 +116,7 @@ export class ListItem extends React.PureComponent {
|
|||
pocket_id={this.props.pocket_id}
|
||||
shim={this.props.shim}
|
||||
bookmarkGuid={this.props.bookmarkGuid}
|
||||
campaignId={this.props.campaignId}
|
||||
flightId={this.props.flightId}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
|
@ -146,7 +146,7 @@ export function _List(props) {
|
|||
<ListItem
|
||||
key={`ds-list-item-${rec.id}`}
|
||||
dispatch={props.dispatch}
|
||||
campaignId={rec.campaign_id}
|
||||
flightId={rec.flight_id}
|
||||
domain={rec.domain}
|
||||
excerpt={rec.excerpt}
|
||||
id={rec.id}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import React from "react";
|
||||
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
|
||||
import { FluentOrText } from "content-src/components/FluentOrText/FluentOrText";
|
||||
|
||||
export class Topic extends React.PureComponent {
|
||||
render() {
|
||||
|
@ -25,7 +26,11 @@ export class Navigation extends React.PureComponent {
|
|||
const header = this.props.header || {};
|
||||
return (
|
||||
<div className={`ds-navigation ds-navigation-${alignment}`}>
|
||||
{header.title ? <div className="ds-header">{header.title}</div> : null}
|
||||
{header.title ? (
|
||||
<FluentOrText message={header.title}>
|
||||
<div className="ds-header" />
|
||||
</FluentOrText>
|
||||
) : null}
|
||||
<div>
|
||||
<ul>
|
||||
{links &&
|
||||
|
|
|
@ -84,7 +84,7 @@ export class _TopSites extends React.PureComponent {
|
|||
label: topSiteSpoc.sponsor,
|
||||
title: topSiteSpoc.sponsor,
|
||||
url: topSiteSpoc.url,
|
||||
campaignId: topSiteSpoc.campaign_id,
|
||||
flightId: topSiteSpoc.flight_id,
|
||||
id: topSiteSpoc.id,
|
||||
guid: topSiteSpoc.id,
|
||||
shim: topSiteSpoc.shim,
|
||||
|
|
|
@ -55,11 +55,11 @@ export class ImpressionStats extends React.PureComponent {
|
|||
const { props } = this;
|
||||
const cards = props.rows;
|
||||
|
||||
if (this.props.campaignId) {
|
||||
if (this.props.flightId) {
|
||||
this.props.dispatch(
|
||||
ac.OnlyToMain({
|
||||
type: at.DISCOVERY_STREAM_SPOC_IMPRESSION,
|
||||
data: { campaignId: this.props.campaignId },
|
||||
data: { flightId: this.props.flightId },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -271,7 +271,7 @@ export class TopSiteLink extends React.PureComponent {
|
|||
{children}
|
||||
{link.type === SPOC_TYPE ? (
|
||||
<ImpressionStats
|
||||
campaignId={link.campaignId}
|
||||
flightId={link.flightId}
|
||||
rows={[
|
||||
{
|
||||
id: link.id,
|
||||
|
|
|
@ -62,9 +62,9 @@ export const LinkMenuOptions = {
|
|||
userEvent: "OPEN_NEW_WINDOW",
|
||||
}),
|
||||
// This blocks the url for regular stories,
|
||||
// but also sends a message to DiscoveryStream with campaign_id.
|
||||
// If DiscoveryStream sees this message for a campaign_id
|
||||
// it also blocks it on the campaign_id.
|
||||
// but also sends a message to DiscoveryStream with flight_id.
|
||||
// If DiscoveryStream sees this message for a flight_id
|
||||
// it also blocks it on the flight_id.
|
||||
BlockUrl: (site, index, eventSource) => ({
|
||||
id: "newtab-menu-dismiss",
|
||||
icon: "dismiss",
|
||||
|
@ -73,7 +73,7 @@ export const LinkMenuOptions = {
|
|||
data: {
|
||||
url: site.open_url || site.url,
|
||||
pocket_id: site.pocket_id,
|
||||
...(site.campaign_id ? { campaign_id: site.campaign_id } : {}),
|
||||
...(site.flight_id ? { flight_id: site.flight_id } : {}),
|
||||
},
|
||||
}),
|
||||
impression: ac.ImpressionStats({
|
||||
|
|
|
@ -2,7 +2,12 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
export const selectLayoutRender = (state, prefs, rickRollCache) => {
|
||||
export const selectLayoutRender = ({
|
||||
state = {},
|
||||
prefs = {},
|
||||
rollCache = [],
|
||||
lang = "",
|
||||
}) => {
|
||||
const { layout, feeds, spocs } = state;
|
||||
let spocIndexMap = {};
|
||||
let bufferRollCache = [];
|
||||
|
@ -23,11 +28,11 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
|
|||
|
||||
// Cache random number for a position
|
||||
let rickRoll;
|
||||
if (!rickRollCache.length) {
|
||||
if (!rollCache.length) {
|
||||
rickRoll = Math.random();
|
||||
bufferRollCache.push(rickRoll);
|
||||
} else {
|
||||
rickRoll = rickRollCache.shift();
|
||||
rickRoll = rollCache.shift();
|
||||
bufferRollCache.push(rickRoll);
|
||||
}
|
||||
|
||||
|
@ -63,6 +68,10 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
|
|||
filterArray.push("TopSites");
|
||||
}
|
||||
|
||||
if (!lang.startsWith("en-")) {
|
||||
filterArray.push("Navigation");
|
||||
}
|
||||
|
||||
if (!prefs["feeds.section.topstories"]) {
|
||||
filterArray.push(...DS_COMPONENTS);
|
||||
}
|
||||
|
@ -213,9 +222,9 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
|
|||
|
||||
const layoutRender = renderLayout();
|
||||
|
||||
// If empty, fill rickRollCache with random probability values from bufferRollCache
|
||||
if (!rickRollCache.length) {
|
||||
rickRollCache.push(...bufferRollCache);
|
||||
// If empty, fill rollCache with random probability values from bufferRollCache
|
||||
if (!rollCache.length) {
|
||||
rollCache.push(...bufferRollCache);
|
||||
}
|
||||
|
||||
// Generate the payload for the SPOCS Fill ping. Note that a SPOC could be rejected
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -178,7 +178,7 @@ Schema definitions/validations that can be used for tests can be found in `syste
|
|||
{"id": 10000, displayed: 0, reason: "frequency_cap", full_recalc: 1},
|
||||
{"id": 10001, displayed: 0, reason: "blocked_by_user", full_recalc: 1},
|
||||
{"id": 10002, displayed: 0, reason: "below_min_score", full_recalc: 1},
|
||||
{"id": 10003, displayed: 0, reason: "campaign_duplicate", full_recalc: 1},
|
||||
{"id": 10003, displayed: 0, reason: "flight_duplicate", full_recalc: 1},
|
||||
{"id": 10004, displayed: 0, reason: "probability_selection", full_recalc: 0},
|
||||
{"id": 10004, displayed: 0, reason: "out_of_position", full_recalc: 0},
|
||||
{"id": 10005, displayed: 1, reason: "n/a", full_recalc: 0}
|
||||
|
@ -264,7 +264,7 @@ and losing focus. | :one:
|
|||
| `message_id` | [required] A string identifier of the message in Activity Stream Router. | :one:
|
||||
| `has_flow_params` | [required] One of [true, false]. A boolean identifier that indicates if Firefox Accounts flow parameters are set or unset. | :one:
|
||||
| `displayed` | [required] 1: a SPOC is displayed; 0: non-displayed | :one:
|
||||
| `reason` | [required] The reason if a SPOC is not displayed, "n/a" for the displayed, one of ("frequency_cap", "blocked_by_user", "campaign_duplicate", "probability_selection", "below_min_score", "out_of_position", "n/a") | :one:
|
||||
| `reason` | [required] The reason if a SPOC is not displayed, "n/a" for the displayed, one of ("frequency_cap", "blocked_by_user", "flight_duplicate", "probability_selection", "below_min_score", "out_of_position", "n/a") | :one:
|
||||
| `full_recalc` | [required] Is it a full SPOCS recalculation: 0: false; 1: true. Recalculation case: 1). fetch SPOCS from Pocket endpoint. Non-recalculation cases: 1). An impression updates the SPOCS; 2). Any action that triggers the `selectLayoutRender ` | :one:
|
||||
|
||||
**Where:**
|
||||
|
|
|
@ -81,13 +81,13 @@ for more information about what methods are available.
|
|||
|
||||
Preferences specific to the Discovery Stream are nested under the sub-branch `browser.newtabpage.activity-stream.discoverystream` (with the exception of `browser.newtabpage.blocked`).
|
||||
|
||||
#### `browser.newtabpage.activity-stream.discoverystream.campaign.blocks`
|
||||
#### `browser.newtabpage.activity-stream.discoverystream.flight.blocks`
|
||||
|
||||
- Type: `string (JSON)`
|
||||
- Default: `{}`
|
||||
- Pref Type: AS
|
||||
|
||||
Not intended for user configuration, but is programmatically updated. Used for tracking blocked campaign IDs when a user dismisses a SPOC. Keys are campaign IDs. Values don't have a specific meaning.
|
||||
Not intended for user configuration, but is programmatically updated. Used for tracking blocked flight IDs when a user dismisses a SPOC. Keys are flight IDs. Values don't have a specific meaning.
|
||||
|
||||
#### `browser.newtabpage.blocked`
|
||||
|
||||
|
@ -108,7 +108,7 @@ Not intended for user configuration, but is programmatically updated. Used for t
|
|||
"enabled": true,
|
||||
"show_spocs": true,
|
||||
"hardcoded_layout": true,
|
||||
"personalized": false,
|
||||
"personalized": true,
|
||||
"layout_endpoint": "https://getpocket.cdn.mozilla.net/v3/newtab/layout?version=1&consumer_key=$apiKey&layout_variant=basic"
|
||||
}
|
||||
```
|
||||
|
@ -119,6 +119,7 @@ Not intended for user configuration, but is programmatically updated. Used for t
|
|||
- `hardcoded_layout` (boolean): When this is true, a hardcoded layout shipped with Firefox will be used instead of a remotely fetched layout definition.
|
||||
- `personalized` (boolean): When this is `true` personalized content based on browsing history will be displayed.
|
||||
- `layout_endpoint` (string): The URL for a remote layout definition that will be used if `hardcoded_layout` is `false`.
|
||||
- `unused_key` (string): This is not set by default and is unused by this codebase. It's a standardized way to differentiate configurations to prevent experiment participants from being unenrolled.
|
||||
|
||||
#### `browser.newtabpage.activity-stream.discoverystream.enabled`
|
||||
|
||||
|
|
|
@ -313,13 +313,6 @@ const PREFS_CONFIG = new Map([
|
|||
value: "https://incoming.telemetry.mozilla.org/submit",
|
||||
},
|
||||
],
|
||||
[
|
||||
"telemetry.ping.endpoint",
|
||||
{
|
||||
title: "Telemetry server endpoint",
|
||||
value: "https://tiles.services.mozilla.com/v4/links/activity-stream",
|
||||
},
|
||||
],
|
||||
[
|
||||
"section.highlights.includeVisited",
|
||||
{
|
||||
|
@ -463,9 +456,9 @@ const PREFS_CONFIG = new Map([
|
|||
],
|
||||
// See browser/app/profile/firefox.js for other ASR preferences. They must be defined there to enable roll-outs.
|
||||
[
|
||||
"discoverystream.campaign.blocks",
|
||||
"discoverystream.flight.blocks",
|
||||
{
|
||||
title: "Track campaign blocks",
|
||||
title: "Track flight blocks",
|
||||
skipBroadcast: true,
|
||||
value: "{}",
|
||||
},
|
||||
|
@ -476,10 +469,11 @@ const PREFS_CONFIG = new Map([
|
|||
title: "Configuration for the new pocket new tab",
|
||||
getValue: ({ geo, locale }) => {
|
||||
// PLEASE NOTE:
|
||||
// hardcoded_layout in `lib/DiscoveryStreamFeed.jsm` only works for en-* and requires refactoring for non english locales
|
||||
// hardcoded_layout in `lib/DiscoveryStreamFeed.jsm` only works for en-* and DE and requires refactoring for other locales
|
||||
const dsEnablementMatrix = {
|
||||
US: ["en-CA", "en-GB", "en-US"],
|
||||
CA: ["en-CA", "en-GB", "en-US"],
|
||||
DE: ["de", "de-DE", "de-AT", "de-CH"],
|
||||
};
|
||||
|
||||
// Verify that the current geo & locale combination is enabled
|
||||
|
|
|
@ -58,11 +58,12 @@ const PREF_IMPRESSION_ID = "browser.newtabpage.activity-stream.impressionId";
|
|||
const PREF_ENABLED = "discoverystream.enabled";
|
||||
const PREF_HARDCODED_BASIC_LAYOUT = "discoverystream.hardcoded-basic-layout";
|
||||
const PREF_SPOCS_ENDPOINT = "discoverystream.spocs-endpoint";
|
||||
const PREF_LANG_LAYOUT_CONFIG = "discoverystream.lang-layout-config";
|
||||
const PREF_TOPSTORIES = "feeds.section.topstories";
|
||||
const PREF_SPOCS_CLEAR_ENDPOINT = "discoverystream.endpointSpocsClear";
|
||||
const PREF_SHOW_SPONSORED = "showSponsored";
|
||||
const PREF_SPOC_IMPRESSIONS = "discoverystream.spoc.impressions";
|
||||
const PREF_CAMPAIGN_BLOCKS = "discoverystream.campaign.blocks";
|
||||
const PREF_FLIGHT_BLOCKS = "discoverystream.flight.blocks";
|
||||
const PREF_REC_IMPRESSIONS = "discoverystream.rec.impressions";
|
||||
|
||||
let getHardcodedLayout;
|
||||
|
@ -74,6 +75,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
|
||||
// Persistent cache for remote endpoint data.
|
||||
this.cache = new PersistentCache(CACHE_KEY, true);
|
||||
this.locale = Services.locale.appLocaleAsLangTag;
|
||||
this._impressionId = this.getOrCreateImpressionId();
|
||||
// Internal in-memory cache for parsing json prefs.
|
||||
this._prefCache = {};
|
||||
|
@ -92,7 +94,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
* Send SPOCS Fill telemetry.
|
||||
* @param {object} filteredItems An object keyed on filter reasons, and the value
|
||||
* is a list of SPOCS.
|
||||
* reasons: blocked_by_user, frequency_cap, below_min_score, campaign_duplicate
|
||||
* reasons: blocked_by_user, frequency_cap, below_min_score, flight_duplicate
|
||||
* @param {boolean} fullRecalc A boolean indicating if it's a full recalculation.
|
||||
* Calling `loadSpocs` will be treated as a full recalculation.
|
||||
* Whereas responding the action "DISCOVERY_STREAM_SPOC_IMPRESSION"
|
||||
|
@ -103,8 +105,8 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
const spocsFill = [];
|
||||
for (const [reason, items] of Object.entries(filteredItems)) {
|
||||
items.forEach(item => {
|
||||
// Only send SPOCS (i.e. it has a campaign_id)
|
||||
if (item.campaign_id) {
|
||||
// Only send SPOCS (i.e. it has a flight_id)
|
||||
if (item.flight_id) {
|
||||
spocsFill.push({ reason, full_recalc, id: item.id, displayed: 0 });
|
||||
}
|
||||
});
|
||||
|
@ -207,7 +209,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
// The server somtimes returns this value already replaced, but we try this for two reasons:
|
||||
// 1. Layout endpoints are not from the server.
|
||||
// 2. Hardcoded layouts don't have this already done for us.
|
||||
const endpoint = rawEndpoint.replace("$apiKey", apiKey);
|
||||
const endpoint = rawEndpoint
|
||||
.replace("$apiKey", apiKey)
|
||||
.replace("$locale", this.locale);
|
||||
|
||||
try {
|
||||
// Make sure the requested endpoint is allowed
|
||||
|
@ -362,12 +366,19 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
}
|
||||
|
||||
if (!layoutResp || !layoutResp.layout) {
|
||||
const langLayoutConfig =
|
||||
this.store.getState().Prefs.values[PREF_LANG_LAYOUT_CONFIG] || "";
|
||||
|
||||
const isBasic =
|
||||
this.config.hardcoded_basic_layout ||
|
||||
this.store.getState().Prefs.values[PREF_HARDCODED_BASIC_LAYOUT] ||
|
||||
!langLayoutConfig
|
||||
.split(",")
|
||||
.find(lang => this.locale.startsWith(lang.trim()));
|
||||
|
||||
// Set a hardcoded layout if one is needed.
|
||||
// Changing values in this layout in memory object is unnecessary.
|
||||
layoutResp = getHardcodedLayout(
|
||||
this.config.hardcoded_basic_layout ||
|
||||
this.store.getState().Prefs.values[PREF_HARDCODED_BASIC_LAYOUT]
|
||||
);
|
||||
layoutResp = getHardcodedLayout(isBasic);
|
||||
}
|
||||
|
||||
sendUpdate({
|
||||
|
@ -588,7 +599,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
},
|
||||
};
|
||||
|
||||
this.cleanUpCampaignImpressionPref(spocsState.spocs);
|
||||
this.cleanUpFlightImpressionPref(spocsState.spocs);
|
||||
await this.cache.set("spocs", spocsState);
|
||||
} else {
|
||||
Cu.reportError("No response for spocs_endpoint prop");
|
||||
|
@ -611,7 +622,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
let frequencyCapped = [];
|
||||
let blockedItems = [];
|
||||
let belowMinScore = [];
|
||||
let campaignDupes = [];
|
||||
let flightDupes = [];
|
||||
this.placementsForEach(placement => {
|
||||
const freshSpocs = spocsState.spocs[placement.name];
|
||||
|
||||
|
@ -619,8 +630,11 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
return;
|
||||
}
|
||||
|
||||
// Migrate flight_id
|
||||
const { data: migratedSpocs } = this.migrateFlightId(freshSpocs);
|
||||
|
||||
const { data: capResult, filtered: caps } = this.frequencyCapSpocs(
|
||||
freshSpocs
|
||||
migratedSpocs
|
||||
);
|
||||
frequencyCapped = [...frequencyCapped, ...caps];
|
||||
|
||||
|
@ -634,10 +648,10 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
);
|
||||
let {
|
||||
below_min_score: minScoreFilter,
|
||||
campaign_duplicate: dupes,
|
||||
flight_duplicate: dupes,
|
||||
} = transformFilter;
|
||||
belowMinScore = [...belowMinScore, ...minScoreFilter];
|
||||
campaignDupes = [...campaignDupes, ...dupes];
|
||||
flightDupes = [...flightDupes, ...dupes];
|
||||
|
||||
spocsState.spocs = {
|
||||
...spocsState.spocs,
|
||||
|
@ -659,7 +673,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
frequency_cap: frequencyCapped,
|
||||
blocked_by_user: blockedItems,
|
||||
below_min_score: belowMinScore,
|
||||
campaign_duplicate: campaignDupes,
|
||||
flight_duplicate: flightDupes,
|
||||
},
|
||||
true
|
||||
);
|
||||
|
@ -770,11 +784,11 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
filterBlocked(data) {
|
||||
const filtered = [];
|
||||
if (data && data.length) {
|
||||
let campaigns = this.readDataPref(PREF_CAMPAIGN_BLOCKS);
|
||||
let flights = this.readDataPref(PREF_FLIGHT_BLOCKS);
|
||||
const filteredItems = data.filter(item => {
|
||||
const blocked =
|
||||
NewTabUtils.blockedLinks.isBlocked({ url: item.url }) ||
|
||||
campaigns[item.campaign_id];
|
||||
flights[item.flight_id];
|
||||
if (blocked) {
|
||||
filtered.push(item);
|
||||
}
|
||||
|
@ -792,33 +806,33 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
if (spocs && spocs.length) {
|
||||
const spocsPerDomain =
|
||||
this.store.getState().DiscoveryStream.spocs.spocs_per_domain || 1;
|
||||
const campaignMap = {};
|
||||
const campaignDuplicates = [];
|
||||
const flightMap = {};
|
||||
const flightDuplicates = [];
|
||||
|
||||
// This order of operations is intended.
|
||||
// scoreItems must be first because it creates this.score.
|
||||
const { data: items, filtered: belowMinScoreItems } = this.scoreItems(
|
||||
spocs
|
||||
);
|
||||
// This removes campaign dupes.
|
||||
// This removes flight dupes.
|
||||
// We do this only after scoring and sorting because that way
|
||||
// we can keep the first item we see, and end up keeping the highest scored.
|
||||
const newSpocs = items.filter(s => {
|
||||
if (!campaignMap[s.campaign_id]) {
|
||||
campaignMap[s.campaign_id] = 1;
|
||||
if (!flightMap[s.flight_id]) {
|
||||
flightMap[s.flight_id] = 1;
|
||||
return true;
|
||||
} else if (campaignMap[s.campaign_id] < spocsPerDomain) {
|
||||
campaignMap[s.campaign_id]++;
|
||||
} else if (flightMap[s.flight_id] < spocsPerDomain) {
|
||||
flightMap[s.flight_id]++;
|
||||
return true;
|
||||
}
|
||||
campaignDuplicates.push(s);
|
||||
flightDuplicates.push(s);
|
||||
return false;
|
||||
});
|
||||
return {
|
||||
data: newSpocs,
|
||||
filtered: {
|
||||
below_min_score: belowMinScoreItems,
|
||||
campaign_duplicate: campaignDuplicates,
|
||||
flight_duplicate: flightDuplicates,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -826,11 +840,42 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
data: spocs,
|
||||
filtered: {
|
||||
below_min_score: [],
|
||||
campaign_duplicate: [],
|
||||
flight_duplicate: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// For backwards compatibility, older spoc endpoint don't have flight_id,
|
||||
// but instead had campaign_id we can use
|
||||
//
|
||||
// @param {Object} data An object that might have a SPOCS array.
|
||||
// @returns {Object} An object with a property `data` as the result.
|
||||
migrateFlightId(spocs) {
|
||||
if (spocs && spocs.length) {
|
||||
return {
|
||||
data: spocs.map(s => {
|
||||
return {
|
||||
...s,
|
||||
...(s.flight_id || s.campaign_id
|
||||
? {
|
||||
flight_id: s.flight_id || s.campaign_id,
|
||||
}
|
||||
: {}),
|
||||
...(s.caps
|
||||
? {
|
||||
caps: {
|
||||
...s.caps,
|
||||
flight: s.caps.flight || s.caps.campaign,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
return { data: spocs };
|
||||
}
|
||||
|
||||
// Filter spocs based on frequency caps
|
||||
//
|
||||
// @param {Object} data An object that might have a SPOCS array.
|
||||
|
@ -859,24 +904,24 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
return { data: spocs, filtered: [] };
|
||||
}
|
||||
|
||||
// Frequency caps are based on campaigns, which may include multiple spocs.
|
||||
// Frequency caps are based on flight, which may include multiple spocs.
|
||||
// We currently support two types of frequency caps:
|
||||
// - lifetime: Indicates how many times spocs from a campaign can be shown in total
|
||||
// - period: Indicates how many times spocs from a campaign can be shown within a period
|
||||
// - lifetime: Indicates how many times spocs from a flight can be shown in total
|
||||
// - period: Indicates how many times spocs from a flight can be shown within a period
|
||||
//
|
||||
// So, for example, the feed configuration below defines that for campaign 1 no more
|
||||
// So, for example, the feed configuration below defines that for flight 1 no more
|
||||
// than 5 spocs can be shown in total, and no more than 2 per hour.
|
||||
// "campaign_id": 1,
|
||||
// "flight_id": 1,
|
||||
// "caps": {
|
||||
// "lifetime": 5,
|
||||
// "campaign": {
|
||||
// "flight": {
|
||||
// "count": 2,
|
||||
// "period": 3600
|
||||
// }
|
||||
// }
|
||||
isBelowFrequencyCap(impressions, spoc) {
|
||||
const campaignImpressions = impressions[spoc.campaign_id];
|
||||
if (!campaignImpressions) {
|
||||
const flightImpressions = impressions[spoc.flight_id];
|
||||
if (!flightImpressions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -886,18 +931,17 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
lifetime || MAX_LIFETIME_CAP,
|
||||
MAX_LIFETIME_CAP
|
||||
);
|
||||
const lifeTimeCapExceeded = campaignImpressions.length >= lifeTimeCap;
|
||||
const lifeTimeCapExceeded = flightImpressions.length >= lifeTimeCap;
|
||||
if (lifeTimeCapExceeded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const campaignCap = spoc.caps && spoc.caps.campaign;
|
||||
if (campaignCap) {
|
||||
const campaignCapExceeded =
|
||||
campaignImpressions.filter(
|
||||
i => Date.now() - i < campaignCap.period * 1000
|
||||
).length >= campaignCap.count;
|
||||
return !campaignCapExceeded;
|
||||
const flightCap = spoc.caps && spoc.caps.flight;
|
||||
if (flightCap) {
|
||||
const flightCapExceeded =
|
||||
flightImpressions.filter(i => Date.now() - i < flightCap.period * 1000)
|
||||
.length >= flightCap.count;
|
||||
return !flightCapExceeded;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1140,7 +1184,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
resetDataPrefs() {
|
||||
this.writeDataPref(PREF_SPOC_IMPRESSIONS, {});
|
||||
this.writeDataPref(PREF_REC_IMPRESSIONS, {});
|
||||
this.writeDataPref(PREF_CAMPAIGN_BLOCKS, {});
|
||||
this.writeDataPref(PREF_FLIGHT_BLOCKS, {});
|
||||
}
|
||||
|
||||
resetState() {
|
||||
|
@ -1164,12 +1208,12 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
}
|
||||
}
|
||||
|
||||
recordCampaignImpression(campaignId) {
|
||||
recordFlightImpression(flightId) {
|
||||
let impressions = this.readDataPref(PREF_SPOC_IMPRESSIONS);
|
||||
|
||||
const timeStamps = impressions[campaignId] || [];
|
||||
const timeStamps = impressions[flightId] || [];
|
||||
timeStamps.push(Date.now());
|
||||
impressions = { ...impressions, [campaignId]: timeStamps };
|
||||
impressions = { ...impressions, [flightId]: timeStamps };
|
||||
|
||||
this.writeDataPref(PREF_SPOC_IMPRESSIONS, impressions);
|
||||
}
|
||||
|
@ -1182,26 +1226,26 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
}
|
||||
}
|
||||
|
||||
recordBlockCampaignId(campaignId) {
|
||||
const campaigns = this.readDataPref(PREF_CAMPAIGN_BLOCKS);
|
||||
if (!campaigns[campaignId]) {
|
||||
campaigns[campaignId] = 1;
|
||||
this.writeDataPref(PREF_CAMPAIGN_BLOCKS, campaigns);
|
||||
recordBlockFlightId(flightId) {
|
||||
const flights = this.readDataPref(PREF_FLIGHT_BLOCKS);
|
||||
if (!flights[flightId]) {
|
||||
flights[flightId] = 1;
|
||||
this.writeDataPref(PREF_FLIGHT_BLOCKS, flights);
|
||||
}
|
||||
}
|
||||
|
||||
cleanUpCampaignImpressionPref(data) {
|
||||
let campaignIds = [];
|
||||
cleanUpFlightImpressionPref(data) {
|
||||
let flightIds = [];
|
||||
this.placementsForEach(placement => {
|
||||
const newSpocs = data[placement.name];
|
||||
if (!newSpocs) {
|
||||
return;
|
||||
}
|
||||
campaignIds = [...campaignIds, ...newSpocs.map(s => `${s.campaign_id}`)];
|
||||
flightIds = [...flightIds, ...newSpocs.map(s => `${s.flight_id}`)];
|
||||
});
|
||||
if (campaignIds && campaignIds.length) {
|
||||
if (flightIds && flightIds.length) {
|
||||
this.cleanUpImpressionPref(
|
||||
id => !campaignIds.includes(id),
|
||||
id => !flightIds.includes(id),
|
||||
PREF_SPOC_IMPRESSIONS
|
||||
);
|
||||
}
|
||||
|
@ -1303,7 +1347,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
break;
|
||||
case at.DISCOVERY_STREAM_SPOC_IMPRESSION:
|
||||
if (this.showSpocs) {
|
||||
this.recordCampaignImpression(action.data.campaignId);
|
||||
this.recordFlightImpression(action.data.flightId);
|
||||
|
||||
// Apply frequency capping to SPOCs in the redux store, only update the
|
||||
// store if the SPOCs are changed.
|
||||
|
@ -1340,7 +1384,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
}
|
||||
}
|
||||
break;
|
||||
// This is fired from the browser, it has no concept of spocs, campaign or pocket.
|
||||
// This is fired from the browser, it has no concept of spocs, flight or pocket.
|
||||
// We match the blocked url with our available spoc urls to see if there is a match.
|
||||
// I suspect we *could* instead do this in BLOCK_URL but I'm not sure.
|
||||
case at.PLACES_LINK_BLOCKED:
|
||||
|
@ -1387,12 +1431,12 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
this.uninitPrefs();
|
||||
break;
|
||||
case at.BLOCK_URL: {
|
||||
// If we block a story that also has a campaign_id
|
||||
// If we block a story that also has a flight_id
|
||||
// we want to record that as blocked too.
|
||||
// This is because a single campaign might have slightly different urls.
|
||||
const { campaign_id } = action.data;
|
||||
if (campaign_id) {
|
||||
this.recordBlockCampaignId(campaign_id);
|
||||
// This is because a single flight might have slightly different urls.
|
||||
const { flight_id } = action.data;
|
||||
if (flight_id) {
|
||||
this.recordBlockFlightId(flight_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1402,6 +1446,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
case PREF_ENABLED:
|
||||
case PREF_HARDCODED_BASIC_LAYOUT:
|
||||
case PREF_SPOCS_ENDPOINT:
|
||||
case PREF_LANG_LAYOUT_CONFIG:
|
||||
// Clear the cached config and broadcast the newly computed value
|
||||
this._prefCache.config = null;
|
||||
this.store.dispatch(
|
||||
|
@ -1452,16 +1497,24 @@ getHardcodedLayout = basic => {
|
|||
{
|
||||
type: "TopSites",
|
||||
header: {
|
||||
title: "Top Sites",
|
||||
title: {
|
||||
id: "newtab-section-header-topsites",
|
||||
},
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
{
|
||||
type: "Message",
|
||||
header: {
|
||||
title: "Recommended by Pocket",
|
||||
title: {
|
||||
id: "newtab-section-header-pocket",
|
||||
values: { provider: "pocket" },
|
||||
},
|
||||
subtitle: "",
|
||||
link_text: "What’s Pocket?",
|
||||
link_text: {
|
||||
id: "newtab-pocket-whats-pocket",
|
||||
values: { provider: "pocket" },
|
||||
},
|
||||
link_url: "https://getpocket.com/firefox/new_tab_learn_more",
|
||||
icon:
|
||||
"resource://activity-stream/data/content/assets/glyph-pocket-16.svg",
|
||||
|
@ -1482,7 +1535,7 @@ getHardcodedLayout = basic => {
|
|||
feed: {
|
||||
embed_reference: null,
|
||||
url:
|
||||
"https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=default_spocs_on",
|
||||
"https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=$locale",
|
||||
},
|
||||
spocs: {
|
||||
probability: 1,
|
||||
|
@ -1548,7 +1601,9 @@ getHardcodedLayout = basic => {
|
|||
{
|
||||
type: "TopSites",
|
||||
header: {
|
||||
title: "Top Sites",
|
||||
title: {
|
||||
id: "newtab-section-header-topsites",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1559,9 +1614,15 @@ getHardcodedLayout = basic => {
|
|||
{
|
||||
type: "Message",
|
||||
header: {
|
||||
title: "Recommended by Pocket",
|
||||
title: {
|
||||
id: "newtab-section-header-pocket",
|
||||
values: { provider: "pocket" },
|
||||
},
|
||||
subtitle: "",
|
||||
link_text: "What’s Pocket?",
|
||||
link_text: {
|
||||
id: "newtab-pocket-whats-pocket",
|
||||
values: { provider: "pocket" },
|
||||
},
|
||||
link_url: "https://getpocket.com/firefox/new_tab_learn_more",
|
||||
icon:
|
||||
"resource://activity-stream/data/content/assets/glyph-pocket-16.svg",
|
||||
|
@ -1587,7 +1648,7 @@ getHardcodedLayout = basic => {
|
|||
feed: {
|
||||
embed_reference: null,
|
||||
url:
|
||||
"https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=en-US&count=30",
|
||||
"https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=$locale&count=30",
|
||||
},
|
||||
spocs: {
|
||||
probability: 1,
|
||||
|
@ -1642,7 +1703,9 @@ getHardcodedLayout = basic => {
|
|||
],
|
||||
},
|
||||
header: {
|
||||
title: "Popular Topics",
|
||||
title: {
|
||||
id: "newtab-pocket-read-more",
|
||||
},
|
||||
},
|
||||
styles: {
|
||||
".ds-navigation": "margin-top: -10px;",
|
||||
|
|
|
@ -102,6 +102,10 @@ this.PrefsFeed = class PrefsFeed {
|
|||
"browser.newtabpage.activity-stream.discoverystream.spocs-endpoint",
|
||||
""
|
||||
);
|
||||
let discoveryStreamLangLayoutConfig = Services.prefs.getStringPref(
|
||||
"browser.newtabpage.activity-stream.discoverystream.lang-layout-config",
|
||||
""
|
||||
);
|
||||
values["discoverystream.enabled"] = discoveryStreamEnabled;
|
||||
this._prefMap.set("discoverystream.enabled", {
|
||||
value: discoveryStreamEnabled,
|
||||
|
@ -116,6 +120,12 @@ this.PrefsFeed = class PrefsFeed {
|
|||
this._prefMap.set("discoverystream.spocs-endpoint", {
|
||||
value: discoveryStreamSpocsEndpoint,
|
||||
});
|
||||
values[
|
||||
"discoverystream.lang-layout-config"
|
||||
] = discoveryStreamLangLayoutConfig;
|
||||
this._prefMap.set("discoverystream.lang-layout-config", {
|
||||
value: discoveryStreamLangLayoutConfig,
|
||||
});
|
||||
|
||||
// Set the initial state of all prefs in redux
|
||||
this.store.dispatch(
|
||||
|
|
|
@ -74,8 +74,6 @@ XPCOMUtils.defineLazyServiceGetters(this, {
|
|||
});
|
||||
|
||||
const ACTIVITY_STREAM_ID = "activity-stream";
|
||||
const ACTIVITY_STREAM_ENDPOINT_PREF =
|
||||
"browser.newtabpage.activity-stream.telemetry.ping.endpoint";
|
||||
const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
|
||||
const DOMWINDOW_UNLOAD_TOPIC = "unload";
|
||||
const TAB_PINNED_EVENT = "TabPinned";
|
||||
|
@ -257,10 +255,7 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
*/
|
||||
get pingCentre() {
|
||||
Object.defineProperty(this, "pingCentre", {
|
||||
value: new PingCentre({
|
||||
topic: ACTIVITY_STREAM_ID,
|
||||
overrideEndpointPref: ACTIVITY_STREAM_ENDPOINT_PREF,
|
||||
}),
|
||||
value: new PingCentre({ topic: ACTIVITY_STREAM_ID }),
|
||||
});
|
||||
return this.pingCentre;
|
||||
}
|
||||
|
@ -420,7 +415,6 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
source,
|
||||
tiles: impressionSets[source],
|
||||
});
|
||||
this.sendEvent(payload);
|
||||
this.sendStructuredIngestionEvent(
|
||||
payload,
|
||||
STRUCTURED_INGESTION_NAMESPACE_AS,
|
||||
|
@ -453,7 +447,6 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
tiles,
|
||||
loaded: tiles.length,
|
||||
});
|
||||
this.sendEvent(payload);
|
||||
this.sendStructuredIngestionEvent(
|
||||
payload,
|
||||
STRUCTURED_INGESTION_NAMESPACE_AS,
|
||||
|
@ -641,11 +634,27 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
}
|
||||
|
||||
sendEvent(event_object) {
|
||||
if (this.telemetryEnabled) {
|
||||
this.pingCentre.sendPing(event_object, { filter: ACTIVITY_STREAM_ID });
|
||||
switch (event_object.action) {
|
||||
case "activity_stream_user_event":
|
||||
this.sendEventPing(event_object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async sendEventPing(ping) {
|
||||
delete ping.action;
|
||||
ping.client_id = await this.telemetryClientId;
|
||||
if (ping.value && typeof ping.value === "object") {
|
||||
ping.value = JSON.stringify(ping.value);
|
||||
}
|
||||
this.sendStructuredIngestionEvent(
|
||||
ping,
|
||||
STRUCTURED_INGESTION_NAMESPACE_AS,
|
||||
"events",
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
sendUTEvent(event_object, eventFunction) {
|
||||
if (this.telemetryEnabled && this.eventTelemetryEnabled) {
|
||||
eventFunction(event_object);
|
||||
|
@ -686,7 +695,6 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
au.getPortIdOfSender(action),
|
||||
action.data
|
||||
);
|
||||
this.sendEvent(payload);
|
||||
this.sendStructuredIngestionEvent(
|
||||
payload,
|
||||
STRUCTURED_INGESTION_NAMESPACE_AS,
|
||||
|
|
|
@ -109,6 +109,9 @@ describe("<DiscoveryStreamBase>", () => {
|
|||
"feeds.topsites": true,
|
||||
},
|
||||
}}
|
||||
document={{
|
||||
documentElement: { lang: "en-US" },
|
||||
}}
|
||||
Sections={[
|
||||
{
|
||||
id: "topstories",
|
||||
|
|
|
@ -146,7 +146,7 @@ describe("<DSLinkMenu>", () => {
|
|||
|
||||
it("should pass through the correct menu options to LinkMenu for spocs", () => {
|
||||
wrapper = shallow(
|
||||
<DSLinkMenu {...ValidDSLinkMenuProps} campaignId="1234" />
|
||||
<DSLinkMenu {...ValidDSLinkMenuProps} flightId="1234" />
|
||||
);
|
||||
wrapper
|
||||
.find(ContextMenuButton)
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { DSMessage } from "content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage";
|
||||
import React from "react";
|
||||
import { SafeAnchor } from "content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
|
||||
import { shallow } from "enzyme";
|
||||
import { FluentOrText } from "content-src/components/FluentOrText/FluentOrText";
|
||||
import { mount } from "enzyme";
|
||||
|
||||
describe("<DSMessage>", () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<DSMessage />);
|
||||
wrapper = mount(<DSMessage />);
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
|
@ -45,4 +46,30 @@ describe("<DSMessage>", () => {
|
|||
SafeAnchor
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a FluentOrText", () => {
|
||||
wrapper.setProps({
|
||||
link_text: "link_text",
|
||||
title: "title",
|
||||
link_url: "https://link_url.com",
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
wrapper
|
||||
.find(".title-text")
|
||||
.children()
|
||||
.at(0)
|
||||
.type(),
|
||||
FluentOrText
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
wrapper
|
||||
.find(".link a")
|
||||
.children()
|
||||
.at(0)
|
||||
.type(),
|
||||
FluentOrText
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -132,12 +132,12 @@ describe("<ImpressionStats>", () => {
|
|||
{ id: 3, pos: 2 },
|
||||
]);
|
||||
});
|
||||
it("should send a DISCOVERY_STREAM_SPOC_IMPRESSION when the wrapped item has a campaignId", () => {
|
||||
it("should send a DISCOVERY_STREAM_SPOC_IMPRESSION when the wrapped item has a flightId", () => {
|
||||
const dispatch = sinon.spy();
|
||||
const campaignId = "a_campaign_id";
|
||||
const flightId = "a_flight_id";
|
||||
const props = {
|
||||
dispatch,
|
||||
campaignId,
|
||||
flightId,
|
||||
IntersectionObserver: buildIntersectionObserver(FullIntersectEntries),
|
||||
};
|
||||
renderImpressionStats(props);
|
||||
|
@ -147,7 +147,7 @@ describe("<ImpressionStats>", () => {
|
|||
|
||||
const [action] = dispatch.secondCall.args;
|
||||
assert.equal(action.type, at.DISCOVERY_STREAM_SPOC_IMPRESSION);
|
||||
assert.deepEqual(action.data, { campaignId });
|
||||
assert.deepEqual(action.data, { flightId });
|
||||
});
|
||||
it("should send an impression when the wrapped item transiting from invisible to visible", () => {
|
||||
const dispatch = sinon.spy();
|
||||
|
|
|
@ -4,13 +4,14 @@ import {
|
|||
} from "content-src/components/DiscoveryStreamComponents/Navigation/Navigation";
|
||||
import React from "react";
|
||||
import { SafeAnchor } from "content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
|
||||
import { shallow } from "enzyme";
|
||||
import { FluentOrText } from "content-src/components/FluentOrText/FluentOrText";
|
||||
import { shallow, mount } from "enzyme";
|
||||
|
||||
describe("<Navigation>", () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<Navigation header={{}} />);
|
||||
wrapper = mount(<Navigation header={{}} />);
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
|
@ -23,6 +24,19 @@ describe("<Navigation>", () => {
|
|||
assert.equal(wrapper.find(".ds-header").text(), "Foo");
|
||||
});
|
||||
|
||||
it("should render a FluentOrText", () => {
|
||||
wrapper.setProps({ header: { title: "Foo" } });
|
||||
|
||||
assert.equal(
|
||||
wrapper
|
||||
.find(".ds-navigation")
|
||||
.children()
|
||||
.at(0)
|
||||
.type(),
|
||||
FluentOrText
|
||||
);
|
||||
});
|
||||
|
||||
it("should render 2 Topics", () => {
|
||||
wrapper.setProps({
|
||||
links: [
|
||||
|
|
|
@ -75,7 +75,7 @@ describe("Discovery Stream <TopSites>", () => {
|
|||
url: "foo",
|
||||
sponsor: "bar",
|
||||
image_src: "foobar",
|
||||
campaign_id: "1234",
|
||||
flight_id: "1234",
|
||||
id: "5678",
|
||||
shim: { impression: "1011" },
|
||||
};
|
||||
|
@ -86,7 +86,7 @@ describe("Discovery Stream <TopSites>", () => {
|
|||
label: "bar",
|
||||
title: "bar",
|
||||
url: "foo",
|
||||
campaignId: "1234",
|
||||
flightId: "1234",
|
||||
id: "5678",
|
||||
guid: "5678",
|
||||
shim: {
|
||||
|
@ -100,7 +100,7 @@ describe("Discovery Stream <TopSites>", () => {
|
|||
label: "bar",
|
||||
title: "bar",
|
||||
url: "foo",
|
||||
campaignId: "1234",
|
||||
flightId: "1234",
|
||||
id: "5678",
|
||||
guid: "5678",
|
||||
shim: {
|
||||
|
@ -142,7 +142,7 @@ describe("Discovery Stream <TopSites>", () => {
|
|||
url: "foo2",
|
||||
sponsor: "bar2",
|
||||
image_src: "foobar2",
|
||||
campaign_id: "1234",
|
||||
flight_id: "1234",
|
||||
id: "5678",
|
||||
shim: { impression: "1011" },
|
||||
},
|
||||
|
@ -161,7 +161,7 @@ describe("Discovery Stream <TopSites>", () => {
|
|||
label: "bar2",
|
||||
title: "bar2",
|
||||
url: "foo2",
|
||||
campaignId: "1234",
|
||||
flightId: "1234",
|
||||
id: "5678",
|
||||
guid: "5678",
|
||||
shim: {
|
||||
|
|
|
@ -29,20 +29,18 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
|
||||
it("should return an empty array given initial state", () => {
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
prefs: {},
|
||||
rollCache: [],
|
||||
});
|
||||
assert.deepEqual(layoutRender, []);
|
||||
});
|
||||
|
||||
it("should return an empty SPOCS fill array given initial state", () => {
|
||||
const { spocsFill } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { spocsFill } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
assert.deepEqual(spocsFill, []);
|
||||
});
|
||||
|
||||
|
@ -57,11 +55,9 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
assert.propertyVal(layoutRender[0], "width", 3);
|
||||
|
@ -80,11 +76,9 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
assert.propertyVal(layoutRender[0], "width", 3);
|
||||
|
@ -107,11 +101,9 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
assert.propertyVal(layoutRender[0], "width", 3);
|
||||
|
@ -137,11 +129,9 @@ describe("selectLayoutRender", () => {
|
|||
data: { lastUpdated: 0, spocs: { spocs: [1, 2, 3] } },
|
||||
});
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
assert.propertyVal(layoutRender[0], "width", 3);
|
||||
|
@ -167,11 +157,9 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.deepEqual(layoutRender[0].components[0].data, {
|
||||
recommendations: [{ id: "bar" }],
|
||||
|
@ -211,11 +199,9 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
const randomStub = globals.sandbox.stub(global.Math, "random").returns(0.1);
|
||||
|
||||
const { spocsFill, layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { spocsFill, layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.calledTwice(randomStub);
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
|
@ -273,11 +259,9 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
const randomStub = globals.sandbox.stub(global.Math, "random").returns(0.1);
|
||||
|
||||
const { spocsFill, layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { spocsFill, layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.calledTwice(randomStub);
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
|
@ -335,11 +319,10 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
const randomStub = globals.sandbox.stub(global.Math, "random");
|
||||
|
||||
const { spocsFill, layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[0.7, 0.3, 0.8]
|
||||
);
|
||||
const { spocsFill, layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
rollCache: [0.7, 0.3, 0.8],
|
||||
});
|
||||
|
||||
assert.notCalled(randomStub);
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
|
@ -404,11 +387,9 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
const randomStub = globals.sandbox.stub(global.Math, "random").returns(0.6);
|
||||
|
||||
const { spocsFill, layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { spocsFill, layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.calledTwice(randomStub);
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
|
@ -468,11 +449,10 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
const randomStub = globals.sandbox.stub(global.Math, "random");
|
||||
|
||||
const { spocsFill, layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[0.4, 0.3]
|
||||
);
|
||||
const { spocsFill, layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
rollCache: [0.4, 0.3],
|
||||
});
|
||||
|
||||
assert.notCalled(randomStub);
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
|
@ -530,11 +510,10 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
const randomStub = globals.sandbox.stub(global.Math, "random");
|
||||
|
||||
const { spocsFill, layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[0.6, 0.7]
|
||||
);
|
||||
const { spocsFill, layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
rollCache: [0.6, 0.7],
|
||||
});
|
||||
|
||||
assert.notCalled(randomStub);
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
|
@ -594,11 +573,10 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
const randomStub = globals.sandbox.stub(global.Math, "random");
|
||||
|
||||
const { spocsFill, layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[0.7, 0.2]
|
||||
);
|
||||
const { spocsFill, layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
rollCache: [0.7, 0.2],
|
||||
});
|
||||
|
||||
assert.notCalled(randomStub);
|
||||
assert.lengthOf(layoutRender, 1);
|
||||
|
@ -652,11 +630,9 @@ describe("selectLayoutRender", () => {
|
|||
});
|
||||
store.dispatch({ type: at.DISCOVERY_STREAM_FEEDS_UPDATE });
|
||||
|
||||
const { spocsFill, layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { spocsFill, layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
const { recommendations } = layoutRender[0].components[0].data;
|
||||
assert.equal(recommendations.length, 4);
|
||||
|
@ -689,11 +665,9 @@ describe("selectLayoutRender", () => {
|
|||
data: { feed: { data: { recommendations: [] } }, url: "foo2.com" },
|
||||
});
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.equal(layoutRender[0].components[0].type, "foo1");
|
||||
assert.equal(layoutRender[0].components[1].type, "foo2");
|
||||
|
@ -733,11 +707,9 @@ describe("selectLayoutRender", () => {
|
|||
data: { feed: { data: { recommendations: [] } }, url: "foo4.com" },
|
||||
});
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.equal(layoutRender[0].components[0].type, "foo1");
|
||||
assert.equal(layoutRender[0].components[1].type, "foo2");
|
||||
|
@ -780,11 +752,9 @@ describe("selectLayoutRender", () => {
|
|||
data: { feed: { data: { recommendations: [] } }, url: "foo4.com" },
|
||||
});
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.equal(layoutRender[0].components[0].type, "foo1");
|
||||
assert.equal(layoutRender[0].components[1].type, "foo2");
|
||||
|
@ -837,11 +807,9 @@ describe("selectLayoutRender", () => {
|
|||
data: fakeSpocsData,
|
||||
});
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.deepEqual(layoutRender[0].components[2].data.recommendations[0], {
|
||||
name: "rec",
|
||||
|
@ -864,11 +832,10 @@ describe("selectLayoutRender", () => {
|
|||
data: { layout: fakeLayout },
|
||||
});
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{ "feeds.topsites": true },
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
prefs: { "feeds.topsites": true },
|
||||
});
|
||||
|
||||
assert.equal(layoutRender[0].components[0].type, "TopSites");
|
||||
assert.equal(layoutRender[1], undefined);
|
||||
|
@ -885,15 +852,42 @@ describe("selectLayoutRender", () => {
|
|||
data: { layout: fakeLayout },
|
||||
});
|
||||
|
||||
const { layoutRender } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{ "feeds.topsites": true },
|
||||
[]
|
||||
);
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
prefs: { "feeds.topsites": true },
|
||||
});
|
||||
|
||||
assert.equal(layoutRender[0].components[0].type, "TopSites");
|
||||
assert.equal(layoutRender[0].components[1], undefined);
|
||||
});
|
||||
it("should not render a Navigation if not en-*", () => {
|
||||
const fakeLayout = [
|
||||
{
|
||||
width: 3,
|
||||
components: [
|
||||
{ type: "Navigation" },
|
||||
{ type: "Message" },
|
||||
{ type: "TopSites" },
|
||||
],
|
||||
},
|
||||
];
|
||||
store.dispatch({
|
||||
type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
|
||||
data: { layout: fakeLayout },
|
||||
});
|
||||
|
||||
const { layoutRender } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
prefs: {
|
||||
"feeds.topsites": true,
|
||||
"feeds.section.topstories": true,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(layoutRender[0].components[0].type, "Message");
|
||||
assert.equal(layoutRender[0].components[1].type, "TopSites");
|
||||
assert.equal(layoutRender[0].components[2], undefined);
|
||||
});
|
||||
it("should skip rendering a spoc in position if that spoc is blocked for that session", () => {
|
||||
const fakeLayout = [
|
||||
{
|
||||
|
@ -930,22 +924,18 @@ describe("selectLayoutRender", () => {
|
|||
data: fakeSpocsData,
|
||||
});
|
||||
|
||||
const { layoutRender: layout1 } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender: layout1 } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
store.dispatch({
|
||||
type: at.DISCOVERY_STREAM_SPOC_BLOCKED,
|
||||
data: { url: "https://foo.com" },
|
||||
});
|
||||
|
||||
const { layoutRender: layout2 } = selectLayoutRender(
|
||||
store.getState().DiscoveryStream,
|
||||
{},
|
||||
[]
|
||||
);
|
||||
const { layoutRender: layout2 } = selectLayoutRender({
|
||||
state: store.getState().DiscoveryStream,
|
||||
});
|
||||
|
||||
assert.deepEqual(layout1[0].components[0].data.recommendations[0], {
|
||||
name: "spoc",
|
||||
|
|
|
@ -68,6 +68,22 @@ describe("ActivityStream", () => {
|
|||
const [, , action] = as.store.init.firstCall.args;
|
||||
assert.equal(action.type, "UNINIT");
|
||||
});
|
||||
it("should clear old default discoverystream config pref", () => {
|
||||
sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
|
||||
sandbox
|
||||
.stub(global.Services.prefs, "getStringPref")
|
||||
.returns(
|
||||
`{"api_key_pref":"extensions.pocket.oAuthConsumerKey","enabled":false,"show_spocs":true,"layout_endpoint":"https://getpocket.cdn.mozilla.net/v3/newtab/layout?version=1&consumer_key=$apiKey&layout_variant=basic"}`
|
||||
);
|
||||
sandbox.stub(global.Services.prefs, "clearUserPref");
|
||||
|
||||
as.init();
|
||||
|
||||
assert.calledWith(
|
||||
global.Services.prefs.clearUserPref,
|
||||
"browser.newtabpage.activity-stream.discoverystream.config"
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
beforeEach(() => {
|
||||
|
@ -183,6 +199,70 @@ describe("ActivityStream", () => {
|
|||
assert.calledWith(global.Services.prefs.clearUserPref, "oldPrefName");
|
||||
});
|
||||
});
|
||||
describe("_updateDynamicPrefs Discovery Stream", () => {
|
||||
it("should be true with expected en-US geo and locale", () => {
|
||||
sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
|
||||
sandbox.stub(global.Services.prefs, "getStringPref").returns("US");
|
||||
sandbox
|
||||
.stub(global.Services.locale, "appLocaleAsLangTag")
|
||||
.get(() => "en-US");
|
||||
|
||||
as._updateDynamicPrefs();
|
||||
|
||||
assert.isTrue(
|
||||
JSON.parse(PREFS_CONFIG.get("discoverystream.config").value).enabled
|
||||
);
|
||||
});
|
||||
it("should be true with expected en-CA geo and locale", () => {
|
||||
sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
|
||||
sandbox.stub(global.Services.prefs, "getStringPref").returns("CA");
|
||||
sandbox
|
||||
.stub(global.Services.locale, "appLocaleAsLangTag")
|
||||
.get(() => "en-CA");
|
||||
|
||||
as._updateDynamicPrefs();
|
||||
|
||||
assert.isTrue(
|
||||
JSON.parse(PREFS_CONFIG.get("discoverystream.config").value).enabled
|
||||
);
|
||||
});
|
||||
it("should be true with expected de geo and locale", () => {
|
||||
sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
|
||||
sandbox.stub(global.Services.prefs, "getStringPref").returns("DE");
|
||||
sandbox
|
||||
.stub(global.Services.locale, "appLocaleAsLangTag")
|
||||
.get(() => "de-DE");
|
||||
|
||||
as._updateDynamicPrefs();
|
||||
|
||||
assert.isTrue(
|
||||
JSON.parse(PREFS_CONFIG.get("discoverystream.config").value).enabled
|
||||
);
|
||||
});
|
||||
it("should be false with no geo and locale", () => {
|
||||
sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
|
||||
sandbox.stub(global.Services.prefs, "getStringPref").returns("NOGEO");
|
||||
|
||||
as._updateDynamicPrefs();
|
||||
|
||||
assert.isFalse(
|
||||
JSON.parse(PREFS_CONFIG.get("discoverystream.config").value).enabled
|
||||
);
|
||||
});
|
||||
it("should be false with weird geo and locale combination", () => {
|
||||
sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
|
||||
sandbox.stub(global.Services.prefs, "getStringPref").returns("DE");
|
||||
sandbox
|
||||
.stub(global.Services.locale, "appLocaleAsLangTag")
|
||||
.get(() => "en-US");
|
||||
|
||||
as._updateDynamicPrefs();
|
||||
|
||||
assert.isFalse(
|
||||
JSON.parse(PREFS_CONFIG.get("discoverystream.config").value).enabled
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("_updateDynamicPrefs topstories default value", () => {
|
||||
it("should be false with no geo/locale", () => {
|
||||
as._updateDynamicPrefs();
|
||||
|
|
|
@ -186,6 +186,18 @@ describe("DiscoveryStreamFeed", () => {
|
|||
{ credentials: "omit" }
|
||||
);
|
||||
});
|
||||
it("should replace locales with $locale", async () => {
|
||||
feed.locale = "replaced";
|
||||
await feed.fetchFromEndpoint(
|
||||
"https://getpocket.cdn.mozilla.net/dummy?locale_lang=$locale"
|
||||
);
|
||||
|
||||
assert.calledWithMatch(
|
||||
fetchStub,
|
||||
"https://getpocket.cdn.mozilla.net/dummy?locale_lang=replaced",
|
||||
{ credentials: "omit" }
|
||||
);
|
||||
});
|
||||
it("should allow POST and with other options", async () => {
|
||||
await feed.fetchFromEndpoint("https://getpocket.cdn.mozilla.net/dummy", {
|
||||
method: "POST",
|
||||
|
@ -315,6 +327,54 @@ describe("DiscoveryStreamFeed", () => {
|
|||
const { layout } = feed.store.getState().DiscoveryStream;
|
||||
assert.equal(layout[0].components[2].properties.items, 3);
|
||||
});
|
||||
it("should use 1 row layout if locale lang doesn't support 7 row layout", async () => {
|
||||
feed.config.hardcoded_layout = true;
|
||||
feed.store = createStore(combineReducers(reducers), {
|
||||
Prefs: {
|
||||
values: {
|
||||
[CONFIG_PREF_NAME]: JSON.stringify({
|
||||
enabled: true,
|
||||
show_spocs: false,
|
||||
layout_endpoint: DUMMY_ENDPOINT,
|
||||
}),
|
||||
[ENDPOINTS_PREF_NAME]: DUMMY_ENDPOINT,
|
||||
"discoverystream.enabled": true,
|
||||
"discoverystream.lang-layout-config": "en",
|
||||
},
|
||||
},
|
||||
});
|
||||
feed.locale = "de-DE";
|
||||
sandbox.stub(feed, "fetchLayout").returns(Promise.resolve(""));
|
||||
|
||||
await feed.loadLayout(feed.store.dispatch);
|
||||
|
||||
const { layout } = feed.store.getState().DiscoveryStream;
|
||||
assert.equal(layout[0].components[2].properties.items, 3);
|
||||
});
|
||||
it("should use 7 row layout if locale lang supports it", async () => {
|
||||
feed.config.hardcoded_layout = true;
|
||||
feed.store = createStore(combineReducers(reducers), {
|
||||
Prefs: {
|
||||
values: {
|
||||
[CONFIG_PREF_NAME]: JSON.stringify({
|
||||
enabled: true,
|
||||
show_spocs: false,
|
||||
layout_endpoint: DUMMY_ENDPOINT,
|
||||
}),
|
||||
[ENDPOINTS_PREF_NAME]: DUMMY_ENDPOINT,
|
||||
"discoverystream.enabled": true,
|
||||
"discoverystream.lang-layout-config": "en,de",
|
||||
},
|
||||
},
|
||||
});
|
||||
feed.locale = "de-DE";
|
||||
sandbox.stub(feed, "fetchLayout").returns(Promise.resolve(""));
|
||||
|
||||
await feed.loadLayout(feed.store.dispatch);
|
||||
|
||||
const { layout } = feed.store.getState().DiscoveryStream;
|
||||
assert.equal(layout[2].components[0].properties.items, 21);
|
||||
});
|
||||
it("should use new spocs endpoint if in the config", async () => {
|
||||
feed.config.spocs_endpoint = "https://spocs.getpocket.com/spocs2";
|
||||
|
||||
|
@ -936,61 +996,61 @@ describe("DiscoveryStreamFeed", () => {
|
|||
});
|
||||
it("should sort based on item_score", () => {
|
||||
const { data: result } = feed.transform([
|
||||
{ id: 2, campaign_id: 2, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 3, campaign_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 1, campaign_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
{ id: 2, flight_id: 2, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 3, flight_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 1, flight_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
]);
|
||||
|
||||
assert.deepEqual(result, [
|
||||
{ id: 1, campaign_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1 },
|
||||
{ id: 2, campaign_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1 },
|
||||
{ id: 3, campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1 },
|
||||
{ id: 1, flight_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1 },
|
||||
{ id: 2, flight_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1 },
|
||||
{ id: 3, flight_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1 },
|
||||
]);
|
||||
});
|
||||
it("should remove items with scores lower than min_score", () => {
|
||||
const { data: result, filtered } = feed.transform([
|
||||
{ id: 2, campaign_id: 2, item_score: 0.8, min_score: 0.9 },
|
||||
{ id: 3, campaign_id: 3, item_score: 0.7, min_score: 0.7 },
|
||||
{ id: 1, campaign_id: 1, item_score: 0.9, min_score: 0.8 },
|
||||
{ id: 2, flight_id: 2, item_score: 0.8, min_score: 0.9 },
|
||||
{ id: 3, flight_id: 3, item_score: 0.7, min_score: 0.7 },
|
||||
{ id: 1, flight_id: 1, item_score: 0.9, min_score: 0.8 },
|
||||
]);
|
||||
|
||||
assert.deepEqual(result, [
|
||||
{ id: 1, campaign_id: 1, item_score: 0.9, score: 0.9, min_score: 0.8 },
|
||||
{ id: 3, campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.7 },
|
||||
{ id: 1, flight_id: 1, item_score: 0.9, score: 0.9, min_score: 0.8 },
|
||||
{ id: 3, flight_id: 3, item_score: 0.7, score: 0.7, min_score: 0.7 },
|
||||
]);
|
||||
|
||||
assert.deepEqual(filtered.below_min_score, [
|
||||
{ id: 2, campaign_id: 2, item_score: 0.8, min_score: 0.9, score: 0.8 },
|
||||
{ id: 2, flight_id: 2, item_score: 0.8, min_score: 0.9, score: 0.8 },
|
||||
]);
|
||||
});
|
||||
it("should add a score prop to spocs", () => {
|
||||
const { data: result } = feed.transform([
|
||||
{ campaign_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
{ flight_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
]);
|
||||
|
||||
assert.equal(result[0].score, 0.9);
|
||||
});
|
||||
it("should filter out duplicate campigns", () => {
|
||||
it("should filter out duplicate flights", () => {
|
||||
const { data: result, filtered } = feed.transform([
|
||||
{ id: 1, campaign_id: 2, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 2, campaign_id: 3, item_score: 0.6, min_score: 0.1 },
|
||||
{ id: 3, campaign_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
{ id: 4, campaign_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 5, campaign_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
{ id: 1, flight_id: 2, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 2, flight_id: 3, item_score: 0.6, min_score: 0.1 },
|
||||
{ id: 3, flight_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
{ id: 4, flight_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 5, flight_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
]);
|
||||
|
||||
assert.deepEqual(result, [
|
||||
{ id: 3, campaign_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1 },
|
||||
{ id: 1, campaign_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1 },
|
||||
{ id: 4, campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1 },
|
||||
{ id: 3, flight_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1 },
|
||||
{ id: 1, flight_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1 },
|
||||
{ id: 4, flight_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1 },
|
||||
]);
|
||||
|
||||
assert.deepEqual(filtered.campaign_duplicate, [
|
||||
{ id: 5, campaign_id: 1, item_score: 0.9, min_score: 0.1, score: 0.9 },
|
||||
{ id: 2, campaign_id: 3, item_score: 0.6, min_score: 0.1, score: 0.6 },
|
||||
assert.deepEqual(filtered.flight_duplicate, [
|
||||
{ id: 5, flight_id: 1, item_score: 0.9, min_score: 0.1, score: 0.9 },
|
||||
{ id: 2, flight_id: 3, item_score: 0.6, min_score: 0.1, score: 0.6 },
|
||||
]);
|
||||
});
|
||||
it("should filter out duplicate campigns while using spocs_per_domain", () => {
|
||||
it("should filter out duplicate flight while using spocs_per_domain", () => {
|
||||
sandbox.stub(feed.store, "getState").returns({
|
||||
DiscoveryStream: {
|
||||
spocs: { spocs_per_domain: 2 },
|
||||
|
@ -998,32 +1058,32 @@ describe("DiscoveryStreamFeed", () => {
|
|||
});
|
||||
|
||||
const { data: result, filtered } = feed.transform([
|
||||
{ id: 1, campaign_id: 2, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 2, campaign_id: 3, item_score: 0.6, min_score: 0.1 },
|
||||
{ id: 3, campaign_id: 1, item_score: 0.6, min_score: 0.1 },
|
||||
{ id: 4, campaign_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 5, campaign_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
{ id: 6, campaign_id: 2, item_score: 0.6, min_score: 0.1 },
|
||||
{ id: 7, campaign_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 8, campaign_id: 1, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 9, campaign_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 10, campaign_id: 1, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 1, flight_id: 2, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 2, flight_id: 3, item_score: 0.6, min_score: 0.1 },
|
||||
{ id: 3, flight_id: 1, item_score: 0.6, min_score: 0.1 },
|
||||
{ id: 4, flight_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 5, flight_id: 1, item_score: 0.9, min_score: 0.1 },
|
||||
{ id: 6, flight_id: 2, item_score: 0.6, min_score: 0.1 },
|
||||
{ id: 7, flight_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 8, flight_id: 1, item_score: 0.8, min_score: 0.1 },
|
||||
{ id: 9, flight_id: 3, item_score: 0.7, min_score: 0.1 },
|
||||
{ id: 10, flight_id: 1, item_score: 0.8, min_score: 0.1 },
|
||||
]);
|
||||
|
||||
assert.deepEqual(result, [
|
||||
{ id: 5, campaign_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1 },
|
||||
{ id: 1, campaign_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1 },
|
||||
{ id: 8, campaign_id: 1, item_score: 0.8, score: 0.8, min_score: 0.1 },
|
||||
{ id: 4, campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1 },
|
||||
{ id: 7, campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1 },
|
||||
{ id: 6, campaign_id: 2, item_score: 0.6, score: 0.6, min_score: 0.1 },
|
||||
{ id: 5, flight_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1 },
|
||||
{ id: 1, flight_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1 },
|
||||
{ id: 8, flight_id: 1, item_score: 0.8, score: 0.8, min_score: 0.1 },
|
||||
{ id: 4, flight_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1 },
|
||||
{ id: 7, flight_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1 },
|
||||
{ id: 6, flight_id: 2, item_score: 0.6, score: 0.6, min_score: 0.1 },
|
||||
]);
|
||||
|
||||
assert.deepEqual(filtered.campaign_duplicate, [
|
||||
{ id: 10, campaign_id: 1, item_score: 0.8, min_score: 0.1, score: 0.8 },
|
||||
{ id: 9, campaign_id: 3, item_score: 0.7, min_score: 0.1, score: 0.7 },
|
||||
{ id: 2, campaign_id: 3, item_score: 0.6, min_score: 0.1, score: 0.6 },
|
||||
{ id: 3, campaign_id: 1, item_score: 0.6, min_score: 0.1, score: 0.6 },
|
||||
assert.deepEqual(filtered.flight_duplicate, [
|
||||
{ id: 10, flight_id: 1, item_score: 0.8, min_score: 0.1, score: 0.8 },
|
||||
{ id: 9, flight_id: 3, item_score: 0.7, min_score: 0.1, score: 0.7 },
|
||||
{ id: 2, flight_id: 3, item_score: 0.6, min_score: 0.1, score: 0.6 },
|
||||
{ id: 3, flight_id: 1, item_score: 0.6, min_score: 0.1, score: 0.6 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -1090,10 +1150,10 @@ describe("DiscoveryStreamFeed", () => {
|
|||
const fakeSpocs = [
|
||||
{
|
||||
id: 1,
|
||||
campaign_id: "seen",
|
||||
flight_id: "seen",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1101,10 +1161,10 @@ describe("DiscoveryStreamFeed", () => {
|
|||
},
|
||||
{
|
||||
id: 2,
|
||||
campaign_id: "not-seen",
|
||||
flight_id: "not-seen",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1119,7 +1179,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
const { data: result, filtered } = feed.frequencyCapSpocs(fakeSpocs);
|
||||
|
||||
assert.equal(result.length, 1);
|
||||
assert.equal(result[0].campaign_id, "not-seen");
|
||||
assert.equal(result[0].flight_id, "not-seen");
|
||||
assert.deepEqual(filtered, [fakeSpocs[0]]);
|
||||
});
|
||||
it("should return simple structure and do nothing with no spocs", () => {
|
||||
|
@ -1130,16 +1190,63 @@ describe("DiscoveryStreamFeed", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#migrateFlightId", () => {
|
||||
it("should migrate campaign to flight if no flight exists", () => {
|
||||
const fakeSpocs = [
|
||||
{
|
||||
id: 1,
|
||||
campaign_id: "campaign",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const { data: result } = feed.migrateFlightId(fakeSpocs);
|
||||
|
||||
assert.deepEqual(result[0], {
|
||||
id: 1,
|
||||
flight_id: "campaign",
|
||||
campaign_id: "campaign",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
campaign: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should not migrate campaign to flight if caps or id don't exist", () => {
|
||||
const fakeSpocs = [{ id: 1 }];
|
||||
const { data: result } = feed.migrateFlightId(fakeSpocs);
|
||||
|
||||
assert.deepEqual(result[0], { id: 1 });
|
||||
});
|
||||
it("should return simple structure and do nothing with no spocs", () => {
|
||||
const { data: result } = feed.migrateFlightId([]);
|
||||
|
||||
assert.equal(result.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#isBelowFrequencyCap", () => {
|
||||
it("should return true if there are no campaign impressions", () => {
|
||||
it("should return true if there are no flight impressions", () => {
|
||||
const fakeImpressions = {
|
||||
seen: [Date.now() - 1],
|
||||
};
|
||||
const fakeSpoc = {
|
||||
campaign_id: "not-seen",
|
||||
flight_id: "not-seen",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1150,12 +1257,12 @@ describe("DiscoveryStreamFeed", () => {
|
|||
|
||||
assert.isTrue(result);
|
||||
});
|
||||
it("should return true if there are no campaign caps", () => {
|
||||
it("should return true if there are no flight caps", () => {
|
||||
const fakeImpressions = {
|
||||
seen: [Date.now() - 1],
|
||||
};
|
||||
const fakeSpoc = {
|
||||
campaign_id: "seen",
|
||||
flight_id: "seen",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
},
|
||||
|
@ -1171,10 +1278,10 @@ describe("DiscoveryStreamFeed", () => {
|
|||
seen: [Date.now() - 1],
|
||||
};
|
||||
const fakeSpoc = {
|
||||
campaign_id: "seen",
|
||||
flight_id: "seen",
|
||||
caps: {
|
||||
lifetime: 1,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 3,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1191,10 +1298,10 @@ describe("DiscoveryStreamFeed", () => {
|
|||
seen: [Date.now() - 1],
|
||||
};
|
||||
const fakeSpoc = {
|
||||
campaign_id: "seen",
|
||||
flight_id: "seen",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1229,12 +1336,12 @@ describe("DiscoveryStreamFeed", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#recordCampaignImpression", () => {
|
||||
describe("#recordFlightImpression", () => {
|
||||
it("should return false if time based cap is hit", () => {
|
||||
sandbox.stub(feed, "readDataPref").returns({});
|
||||
sandbox.stub(feed, "writeDataPref").returns();
|
||||
|
||||
feed.recordCampaignImpression("seen");
|
||||
feed.recordFlightImpression("seen");
|
||||
|
||||
assert.calledWith(feed.writeDataPref, SPOC_IMPRESSION_TRACKING_PREF, {
|
||||
seen: [0],
|
||||
|
@ -1242,40 +1349,40 @@ describe("DiscoveryStreamFeed", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#recordBlockCampaignId", () => {
|
||||
it("should call writeDataPref with new campaign id added", () => {
|
||||
describe("#recordBlockFlightId", () => {
|
||||
it("should call writeDataPref with new flight id added", () => {
|
||||
sandbox.stub(feed, "readDataPref").returns({ "1234": 1 });
|
||||
sandbox.stub(feed, "writeDataPref").returns();
|
||||
|
||||
feed.recordBlockCampaignId("5678");
|
||||
feed.recordBlockFlightId("5678");
|
||||
|
||||
assert.calledOnce(feed.readDataPref);
|
||||
assert.calledWith(feed.writeDataPref, "discoverystream.campaign.blocks", {
|
||||
assert.calledWith(feed.writeDataPref, "discoverystream.flight.blocks", {
|
||||
"1234": 1,
|
||||
"5678": 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#cleanUpCampaignImpressionPref", () => {
|
||||
it("should remove campaign-3 because it is no longer being used", async () => {
|
||||
describe("#cleanUpFlightImpressionPref", () => {
|
||||
it("should remove flight-3 because it is no longer being used", async () => {
|
||||
const fakeSpocs = {
|
||||
spocs: [
|
||||
{
|
||||
campaign_id: "campaign-1",
|
||||
flight_id: "flight-1",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
campaign_id: "campaign-2",
|
||||
flight_id: "flight-2",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1284,16 +1391,16 @@ describe("DiscoveryStreamFeed", () => {
|
|||
],
|
||||
};
|
||||
const fakeImpressions = {
|
||||
"campaign-2": [Date.now() - 1],
|
||||
"campaign-3": [Date.now() - 1],
|
||||
"flight-2": [Date.now() - 1],
|
||||
"flight-3": [Date.now() - 1],
|
||||
};
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "writeDataPref").returns();
|
||||
|
||||
feed.cleanUpCampaignImpressionPref(fakeSpocs);
|
||||
feed.cleanUpFlightImpressionPref(fakeSpocs);
|
||||
|
||||
assert.calledWith(feed.writeDataPref, SPOC_IMPRESSION_TRACKING_PREF, {
|
||||
"campaign-2": [-1],
|
||||
"flight-2": [-1],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1416,10 +1523,10 @@ describe("DiscoveryStreamFeed", () => {
|
|||
spocs: [
|
||||
{
|
||||
id: 1,
|
||||
campaign_id: "seen",
|
||||
flight_id: "seen",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1427,10 +1534,10 @@ describe("DiscoveryStreamFeed", () => {
|
|||
},
|
||||
{
|
||||
id: 2,
|
||||
campaign_id: "not-seen",
|
||||
flight_id: "not-seen",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1457,10 +1564,10 @@ describe("DiscoveryStreamFeed", () => {
|
|||
spocs: [
|
||||
{
|
||||
id: 2,
|
||||
campaign_id: "not-seen",
|
||||
flight_id: "not-seen",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1477,13 +1584,13 @@ describe("DiscoveryStreamFeed", () => {
|
|||
},
|
||||
];
|
||||
|
||||
sandbox.stub(feed, "recordCampaignImpression").returns();
|
||||
sandbox.stub(feed, "recordFlightImpression").returns();
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.spy(feed.store, "dispatch");
|
||||
|
||||
await feed.onAction({
|
||||
type: at.DISCOVERY_STREAM_SPOC_IMPRESSION,
|
||||
data: { campaign_id: "seen" },
|
||||
data: { flight_id: "seen" },
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
|
@ -1498,13 +1605,13 @@ describe("DiscoveryStreamFeed", () => {
|
|||
it("should not call dispatch to ac.AlsoToPreloaded if spocs were not changed by frequency capping", async () => {
|
||||
Object.defineProperty(feed, "showSpocs", { get: () => true });
|
||||
const fakeImpressions = {};
|
||||
sandbox.stub(feed, "recordCampaignImpression").returns();
|
||||
sandbox.stub(feed, "recordFlightImpression").returns();
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.spy(feed.store, "dispatch");
|
||||
|
||||
await feed.onAction({
|
||||
type: at.DISCOVERY_STREAM_SPOC_IMPRESSION,
|
||||
data: { campaign_id: "seen" },
|
||||
data: { flight_id: "seen" },
|
||||
});
|
||||
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
|
@ -1513,7 +1620,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
sandbox.restore();
|
||||
Object.defineProperty(feed, "showSpocs", { get: () => true });
|
||||
const fakeImpressions = {};
|
||||
sandbox.stub(feed, "recordCampaignImpression").returns();
|
||||
sandbox.stub(feed, "recordFlightImpression").returns();
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.spy(feed.store, "dispatch");
|
||||
sandbox.spy(feed, "frequencyCapSpocs");
|
||||
|
@ -1522,10 +1629,10 @@ describe("DiscoveryStreamFeed", () => {
|
|||
spocs: [
|
||||
{
|
||||
id: 2,
|
||||
campaign_id: "seen-2",
|
||||
flight_id: "seen-2",
|
||||
caps: {
|
||||
lifetime: 3,
|
||||
campaign: {
|
||||
flight: {
|
||||
count: 1,
|
||||
period: 1,
|
||||
},
|
||||
|
@ -1545,7 +1652,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
|
||||
await feed.onAction({
|
||||
type: at.DISCOVERY_STREAM_SPOC_IMPRESSION,
|
||||
data: { campaign_id: "doesn't matter" },
|
||||
data: { flight_id: "doesn't matter" },
|
||||
});
|
||||
|
||||
assert.calledOnce(feed.frequencyCapSpocs);
|
||||
|
@ -1559,12 +1666,12 @@ describe("DiscoveryStreamFeed", () => {
|
|||
spocs: [
|
||||
{
|
||||
id: 1,
|
||||
campaign_id: "foo",
|
||||
flight_id: "foo",
|
||||
url: "foo.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
campaign_id: "bar",
|
||||
flight_id: "bar",
|
||||
url: "bar.com",
|
||||
},
|
||||
],
|
||||
|
@ -1638,17 +1745,17 @@ describe("DiscoveryStreamFeed", () => {
|
|||
});
|
||||
|
||||
describe("#onAction: BLOCK_URL", () => {
|
||||
it("should call recordBlockCampaignId whith BLOCK_URL", async () => {
|
||||
sandbox.stub(feed, "recordBlockCampaignId").returns();
|
||||
it("should call recordBlockFlightId whith BLOCK_URL", async () => {
|
||||
sandbox.stub(feed, "recordBlockFlightId").returns();
|
||||
|
||||
await feed.onAction({
|
||||
type: at.BLOCK_URL,
|
||||
data: {
|
||||
campaign_id: "1234",
|
||||
flight_id: "1234",
|
||||
},
|
||||
});
|
||||
|
||||
assert.calledWith(feed.recordBlockCampaignId, "1234");
|
||||
assert.calledWith(feed.recordBlockFlightId, "1234");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2354,19 +2461,16 @@ describe("DiscoveryStreamFeed", () => {
|
|||
{ id: 2, reason: "frequency_cap", displayed: 0, full_recalc: 1 },
|
||||
{ id: 3, reason: "blocked_by_user", displayed: 0, full_recalc: 1 },
|
||||
{ id: 4, reason: "blocked_by_user", displayed: 0, full_recalc: 1 },
|
||||
{ id: 5, reason: "campaign_duplicate", displayed: 0, full_recalc: 1 },
|
||||
{ id: 6, reason: "campaign_duplicate", displayed: 0, full_recalc: 1 },
|
||||
{ id: 5, reason: "flight_duplicate", displayed: 0, full_recalc: 1 },
|
||||
{ id: 6, reason: "flight_duplicate", displayed: 0, full_recalc: 1 },
|
||||
{ id: 7, reason: "below_min_score", displayed: 0, full_recalc: 1 },
|
||||
{ id: 8, reason: "below_min_score", displayed: 0, full_recalc: 1 },
|
||||
];
|
||||
const filtered = {
|
||||
frequency_cap: [{ id: 1, campaign_id: 1 }, { id: 2, campaign_id: 2 }],
|
||||
blocked_by_user: [{ id: 3, campaign_id: 3 }, { id: 4, campaign_id: 4 }],
|
||||
campaign_duplicate: [
|
||||
{ id: 5, campaign_id: 5 },
|
||||
{ id: 6, campaign_id: 6 },
|
||||
],
|
||||
below_min_score: [{ id: 7, campaign_id: 7 }, { id: 8, campaign_id: 8 }],
|
||||
frequency_cap: [{ id: 1, flight_id: 1 }, { id: 2, flight_id: 2 }],
|
||||
blocked_by_user: [{ id: 3, flight_id: 3 }, { id: 4, flight_id: 4 }],
|
||||
flight_duplicate: [{ id: 5, flight_id: 5 }, { id: 6, flight_id: 6 }],
|
||||
below_min_score: [{ id: 7, flight_id: 7 }, { id: 8, flight_id: 8 }],
|
||||
};
|
||||
feed._sendSpocsFill(filtered, true);
|
||||
|
||||
|
@ -2382,7 +2486,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
{ id: 2, reason: "frequency_cap", displayed: 0, full_recalc: 0 },
|
||||
];
|
||||
const filtered = {
|
||||
frequency_cap: [{ id: 1, campaign_id: 1 }, { id: 2, campaign_id: 2 }],
|
||||
frequency_cap: [{ id: 1, flight_id: 1 }, { id: 2, flight_id: 2 }],
|
||||
};
|
||||
feed._sendSpocsFill(filtered, false);
|
||||
|
||||
|
@ -2396,14 +2500,14 @@ describe("DiscoveryStreamFeed", () => {
|
|||
const expected = [
|
||||
{ id: 1, reason: "frequency_cap", displayed: 0, full_recalc: 1 },
|
||||
{ id: 3, reason: "blocked_by_user", displayed: 0, full_recalc: 1 },
|
||||
{ id: 5, reason: "campaign_duplicate", displayed: 0, full_recalc: 1 },
|
||||
{ id: 5, reason: "flight_duplicate", displayed: 0, full_recalc: 1 },
|
||||
{ id: 7, reason: "below_min_score", displayed: 0, full_recalc: 1 },
|
||||
];
|
||||
const filtered = {
|
||||
frequency_cap: [{ id: 1, campaign_id: 1 }, { id: 2 }],
|
||||
blocked_by_user: [{ id: 3, campaign_id: 3 }, { id: 4 }],
|
||||
campaign_duplicate: [{ id: 5, campaign_id: 5 }, { id: 6 }],
|
||||
below_min_score: [{ id: 7, campaign_id: 7 }, { id: 8 }],
|
||||
frequency_cap: [{ id: 1, flight_id: 1 }, { id: 2 }],
|
||||
blocked_by_user: [{ id: 3, flight_id: 3 }, { id: 4 }],
|
||||
flight_duplicate: [{ id: 5, flight_id: 5 }, { id: 6 }],
|
||||
below_min_score: [{ id: 7, flight_id: 7 }, { id: 8 }],
|
||||
};
|
||||
feed._sendSpocsFill(filtered, true);
|
||||
|
||||
|
|
|
@ -974,16 +974,53 @@ describe("TelemetryFeed", () => {
|
|||
assert.propertyVal(ping, "event_context", "foo");
|
||||
});
|
||||
});
|
||||
describe("#sendEvent", () => {
|
||||
it("should call PingCentre", async () => {
|
||||
FakePrefs.prototype.prefs.telemetry = true;
|
||||
const event = {};
|
||||
describe("#sendEventPing", () => {
|
||||
it("should call sendStructuredIngestionEvent", async () => {
|
||||
const data = {
|
||||
action: "activity_stream_user_event",
|
||||
event: "CLICK",
|
||||
};
|
||||
instance = new TelemetryFeed();
|
||||
sandbox.stub(instance.pingCentre, "sendPing");
|
||||
sandbox.spy(instance, "sendStructuredIngestionEvent");
|
||||
|
||||
await instance.sendEvent(event);
|
||||
await instance.sendEventPing(data);
|
||||
|
||||
assert.calledWith(instance.pingCentre.sendPing, event);
|
||||
const expectedPayload = {
|
||||
client_id: FAKE_TELEMETRY_ID,
|
||||
event: "CLICK",
|
||||
};
|
||||
assert.calledWith(instance.sendStructuredIngestionEvent, expectedPayload);
|
||||
});
|
||||
it("should stringify value if it is an Object", async () => {
|
||||
const data = {
|
||||
action: "activity_stream_user_event",
|
||||
event: "CLICK",
|
||||
value: { foo: "bar" },
|
||||
};
|
||||
instance = new TelemetryFeed();
|
||||
sandbox.spy(instance, "sendStructuredIngestionEvent");
|
||||
|
||||
await instance.sendEventPing(data);
|
||||
|
||||
const expectedPayload = {
|
||||
client_id: FAKE_TELEMETRY_ID,
|
||||
event: "CLICK",
|
||||
value: JSON.stringify({ foo: "bar" }),
|
||||
};
|
||||
assert.calledWith(instance.sendStructuredIngestionEvent, expectedPayload);
|
||||
});
|
||||
});
|
||||
describe("#sendEvent", () => {
|
||||
it("should call sendEventPing on activity_stream_user_event", () => {
|
||||
FakePrefs.prototype.prefs.telemetry = true;
|
||||
FakePrefs.prototype.prefs[STRUCTURED_INGESTION_TELEMETRY_PREF] = true;
|
||||
const event = { action: "activity_stream_user_event" };
|
||||
instance = new TelemetryFeed();
|
||||
sandbox.spy(instance, "sendEventPing");
|
||||
|
||||
instance.sendEvent(event);
|
||||
|
||||
assert.calledOnce(instance.sendEventPing);
|
||||
});
|
||||
});
|
||||
describe("#sendUTEvent", () => {
|
||||
|
@ -1255,7 +1292,7 @@ describe("TelemetryFeed", () => {
|
|||
assert.calledWith(sendEvent, eventCreator.returnValue);
|
||||
});
|
||||
it("should send an event on a TELEMETRY_IMPRESSION_STATS action", () => {
|
||||
const sendEvent = sandbox.stub(instance, "sendEvent");
|
||||
const sendEvent = sandbox.stub(instance, "sendStructuredIngestionEvent");
|
||||
const eventCreator = sandbox.stub(instance, "createImpressionStats");
|
||||
const tiles = [{ id: 10001 }, { id: 10002 }, { id: 10003 }];
|
||||
const action = ac.ImpressionStats({ source: "POCKET", tiles });
|
||||
|
@ -1438,7 +1475,7 @@ describe("TelemetryFeed", () => {
|
|||
assert.notCalled(spy);
|
||||
});
|
||||
it("should send impression pings if there is impression data", () => {
|
||||
const spy = sandbox.spy(instance, "sendEvent");
|
||||
const spy = sandbox.spy(instance, "sendStructuredIngestionEvent");
|
||||
const session = {
|
||||
impressionSets: {
|
||||
source_foo: [{ id: 1, pos: 0 }, { id: 2, pos: 1 }],
|
||||
|
@ -1459,7 +1496,7 @@ describe("TelemetryFeed", () => {
|
|||
assert.notCalled(spy);
|
||||
});
|
||||
it("should send loaded content pings if there is loaded content data", () => {
|
||||
const spy = sandbox.spy(instance, "sendEvent");
|
||||
const spy = sandbox.spy(instance, "sendStructuredIngestionEvent");
|
||||
const session = {
|
||||
loadedContentSets: {
|
||||
source_foo: [{ id: 1, pos: 0 }, { id: 2, pos: 1 }],
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
|
@ -18,11 +14,6 @@ ChromeUtils.defineModuleGetter(
|
|||
"UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ClientID",
|
||||
"resource://gre/modules/ClientID.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"TelemetryEnvironment",
|
||||
|
@ -38,7 +29,6 @@ const PREF_BRANCH = "browser.ping-centre.";
|
|||
|
||||
const TELEMETRY_PREF = `${PREF_BRANCH}telemetry`;
|
||||
const LOGGING_PREF = `${PREF_BRANCH}log`;
|
||||
const PRODUCTION_ENDPOINT_PREF = `${PREF_BRANCH}production.endpoint`;
|
||||
const STRUCTURED_INGESTION_SEND_TIMEOUT = 30 * 1000; // 30 seconds
|
||||
|
||||
const FHR_UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
|
||||
|
@ -190,7 +180,6 @@ const REGION_WHITELIST = new Set([
|
|||
* @param {Object} options
|
||||
* @param {string} options.topic - a unique ID for users of PingCentre to distinguish
|
||||
* their data on the server side.
|
||||
* @param {string} options.overrideEndpointPref - optional pref for URL where the POST is sent.
|
||||
*/
|
||||
class PingCentre {
|
||||
constructor(options) {
|
||||
|
@ -201,8 +190,6 @@ class PingCentre {
|
|||
this._topic = options.topic;
|
||||
this._prefs = Services.prefs.getBranch("");
|
||||
|
||||
this._setPingEndpoint(options.topic, options.overrideEndpointPref);
|
||||
|
||||
this._enabled = this._prefs.getBoolPref(TELEMETRY_PREF);
|
||||
this._onTelemetryPrefChange = this._onTelemetryPrefChange.bind(this);
|
||||
this._prefs.addObserver(TELEMETRY_PREF, this._onTelemetryPrefChange);
|
||||
|
@ -216,30 +203,10 @@ class PingCentre {
|
|||
this._prefs.addObserver(LOGGING_PREF, this._onLoggingPrefChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily get the Telemetry id promise
|
||||
*/
|
||||
get telemetryClientId() {
|
||||
Object.defineProperty(this, "telemetryClientId", {
|
||||
value: ClientID.getClientID(),
|
||||
});
|
||||
return this.telemetryClientId;
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return this._enabled && this._fhrEnabled;
|
||||
}
|
||||
|
||||
_setPingEndpoint(topic, overrideEndpointPref) {
|
||||
const overrideValue =
|
||||
overrideEndpointPref && this._prefs.getStringPref(overrideEndpointPref);
|
||||
if (overrideValue) {
|
||||
this._pingEndpoint = overrideValue;
|
||||
} else {
|
||||
this._pingEndpoint = this._prefs.getStringPref(PRODUCTION_ENDPOINT_PREF);
|
||||
}
|
||||
}
|
||||
|
||||
_onLoggingPrefChange(aSubject, aTopic, prefKey) {
|
||||
this.logging = this._prefs.getBoolPref(prefKey);
|
||||
}
|
||||
|
@ -284,37 +251,6 @@ class PingCentre {
|
|||
return region;
|
||||
}
|
||||
|
||||
async _createPing(data, options) {
|
||||
let filter = options && options.filter;
|
||||
let experiments = TelemetryEnvironment.getActiveExperiments();
|
||||
let experimentsString = this._createExperimentsString(experiments, filter);
|
||||
|
||||
let clientID = data.client_id || (await this.telemetryClientId);
|
||||
let locale = data.locale || Services.locale.appLocaleAsLangTag;
|
||||
let profileCreationDate =
|
||||
TelemetryEnvironment.currentEnvironment.profile.resetDate ||
|
||||
TelemetryEnvironment.currentEnvironment.profile.creationDate;
|
||||
const payload = Object.assign(
|
||||
{
|
||||
locale,
|
||||
topic: this._topic,
|
||||
client_id: clientID,
|
||||
version: AppConstants.MOZ_APP_VERSION,
|
||||
release_channel: UpdateUtils.getUpdateChannel(false),
|
||||
},
|
||||
data
|
||||
);
|
||||
if (experimentsString) {
|
||||
payload.shield_id = experimentsString;
|
||||
}
|
||||
if (profileCreationDate) {
|
||||
payload.profile_creation_date = profileCreationDate;
|
||||
}
|
||||
payload.region = this._getRegion();
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
_createStructuredIngestionPing(data, options = {}) {
|
||||
let { filter } = options;
|
||||
let experiments = TelemetryEnvironment.getActiveExperiments();
|
||||
|
@ -336,39 +272,6 @@ class PingCentre {
|
|||
return payload;
|
||||
}
|
||||
|
||||
async sendPing(data, options) {
|
||||
if (!this.enabled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const payload = await this._createPing(data, options);
|
||||
|
||||
if (this.logging) {
|
||||
// performance related pings cause a lot of logging, so we mute them
|
||||
if (data.action !== "activity_stream_performance") {
|
||||
Services.console.logStringMessage(
|
||||
`TELEMETRY PING: ${JSON.stringify(payload)}\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(this._pingEndpoint, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
credentials: "omit",
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
Cu.reportError(
|
||||
`Ping failure with HTTP response code: ${response.status}`
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
Cu.reportError(`Ping failure with error: ${e}`);
|
||||
});
|
||||
}
|
||||
|
||||
static _gzipCompressString(string) {
|
||||
let observer = {
|
||||
buffer: "",
|
||||
|
@ -483,7 +386,6 @@ class PingCentre {
|
|||
|
||||
this.PingCentre = PingCentre;
|
||||
this.PingCentreConstants = {
|
||||
PRODUCTION_ENDPOINT_PREF,
|
||||
FHR_UPLOAD_ENABLED_PREF,
|
||||
TELEMETRY_PREF,
|
||||
LOGGING_PREF,
|
||||
|
|
Загрузка…
Ссылка в новой задаче