Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

feat(pocket): Use geo and locale to determine when to enable Pocket #3021

Merged
merged 1 commit into from
Jul 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions docs/v2-system-addon/preferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ All preferences for Activity Stream should be defined in the `PREFS_CONFIG` Arra
found in [lib/ActivityStream.jsm](../../system-addon/lib/ActivityStream.jsm).
The configuration object should have a `name` (the name of the pref), a `title`
that describes the functionality of the pref, and a `value`, the default value
of the pref. For example:
of the pref. Optionally a `getValue` function can be provided to dynamically
generate a default pref value based on args, e.g., geo and locale. For
developers-specific defaults, an optional `value_local_dev` will be used instead
of `value`. For example:

```js
{
name: "telemetry.log",
title: "Log telemetry events in the console",
value: false
value: false,
value_local_dev: true,
getValue: ({geo}) => geo === "CA"
}
```

Expand Down
178 changes: 114 additions & 64 deletions system-addon/lib/ActivityStream.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,46 @@ const {TelemetryFeed} = Cu.import("resource://activity-stream/lib/TelemetryFeed.
const {TopSitesFeed} = Cu.import("resource://activity-stream/lib/TopSitesFeed.jsm", {});
const {TopStoriesFeed} = Cu.import("resource://activity-stream/lib/TopStoriesFeed.jsm", {});

const GEO_PREF = "browser.search.region";
const REASON_ADDON_UNINSTALL = 6;

// For now, we only want to show top stories by default to the following locales
const showTopStoriesByDefault = ["en-US", "en-CA"].includes(Services.locale.getRequestedLocale());
// Sections, keyed by section id
const SECTIONS = new Map([
["topstories", {
feed: TopStoriesFeed,
prefTitle: "Fetches content recommendations from a configurable content provider",
showByDefault: showTopStoriesByDefault
}]
]);

const SECTION_FEEDS_CONFIG = Array.from(SECTIONS.entries()).map(entry => {
const id = entry[0];
const {feed: Feed, prefTitle, showByDefault: value} = entry[1];
return {
name: `section.${id}`,
factory: () => new Feed(),
title: prefTitle || `${id} section feed`,
value
};
});

// Configure default Activity Stream prefs with a plain `value` or a `getValue`
// that computes a value. A `value_local_dev` is used for development defaults.
const PREFS_CONFIG = new Map([
["default.sites", {
title: "Comma-separated list of default top sites to fill in behind visited sites",
value: "https://www.facebook.com/,https://www.youtube.com/,https://www.amazon.com/,https://www.yahoo.com/,https://www.ebay.com/,https://twitter.com/"
}],
["feeds.section.topstories.options", {
title: "Configuration options for top stories feed",
// This is a dynamic pref as it depends on the feed being shown or not
getValue: args => JSON.stringify({
api_key_pref: "extensions.pocket.oAuthConsumerKey",
// Use the opposite value as what default value the feed would have used
hidden: !PREFS_CONFIG.get("feeds.section.topstories").getValue(args),
learn_more_endpoint: "https://getpocket.com/firefox_learnmore?src=ff_newtab",
provider_description: "pocket_feedback_body",
provider_icon: "pocket",
provider_name: "Pocket",
read_more_endpoint: "https://getpocket.com/explore/trending?src=ff_new_tab",
stories_endpoint: "https://getpocket.com/v3/firefox/global-recs?consumer_key=$apiKey&locale_lang=$locale",
stories_referrer: "https://getpocket.com/recommendations",
survey_link: "https://www.surveymonkey.com/r/newtabffx",
topics_endpoint: "https://getpocket.com/v3/firefox/trending-topics?consumer_key=$apiKey&locale_lang=$locale"
Copy link
Member Author

@Mardak Mardak Jul 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose with valueFunc, at some point we could just insert args.locale directly into these endpoints instead of having TopStoriesFeed separately get the locale and insert it from #2991. @csadilek

Theoretically valueFunc could be called when locale changes too (it doesn't do it in this PR), but that also means TopStoriesFeed wouldn't need to watch for locale changes as its prefs will just update.

And if Pocket wants a client-provided geo (in addition to its own server computed geo from IP address), this would be a natural place to insert it too. E.g., this would support detecting, Firefox says it's a CA/Canada geo, but the current IP address is in San Francisco, so maybe this user is traveling.

})
}],
["migrationExpired", {
title: "Boolean flag that decides whether to show the migration message or not.",
value: false
}],
["migrationLastShownDate", {
title: "Timestamp when migration message was last shown. In seconds.",
value: 0
}],
["migrationRemainingDays", {
title: "Number of days to show the manual migration message",
value: 4
}],
["showSearch", {
title: "Show the Search bar on the New Tab page",
value: true
Expand All @@ -72,45 +83,23 @@ const PREFS_CONFIG = new Map([
["telemetry.ping.endpoint", {
title: "Telemetry server endpoint",
value: "https://tiles.services.mozilla.com/v4/links/activity-stream"
}],
["feeds.section.topstories.options", {
title: "Configuration options for top stories feed",
value: `{
"stories_endpoint": "https://getpocket.com/v3/firefox/global-recs?consumer_key=$apiKey&locale_lang=$locale",
"stories_referrer": "https://getpocket.com/recommendations",
"topics_endpoint": "https://getpocket.com/v3/firefox/trending-topics?consumer_key=$apiKey&locale_lang=$locale",
"read_more_endpoint": "https://getpocket.com/explore/trending?src=ff_new_tab",
"learn_more_endpoint": "https://getpocket.com/firefox_learnmore?src=ff_newtab",
"survey_link": "https://www.surveymonkey.com/r/newtabffx",
"api_key_pref": "extensions.pocket.oAuthConsumerKey",
"provider_name": "Pocket",
"provider_icon": "pocket",
"provider_description": "pocket_feedback_body",
"hidden": ${!showTopStoriesByDefault}
}`
}],
["migrationExpired", {
title: "Boolean flag that decides whether to show the migration message or not.",
value: false
}],
["migrationRemainingDays", {
title: "Number of days to show the manual migration message",
value: 4
}],
["migrationLastShownDate", {
title: "Timestamp when migration message was last shown. In seconds.",
value: 0
}]
]);

const FEEDS_CONFIG = new Map();
for (const {name, factory, title, value} of SECTION_FEEDS_CONFIG.concat([
// Array of each feed's FEEDS_CONFIG factory and values to add to PREFS_CONFIG
const FEEDS_DATA = [
{
name: "localization",
factory: () => new LocalizationFeed(),
title: "Initialize strings and detect locale for Activity Stream",
value: true
},
{
name: "migration",
factory: () => new ManualMigration(),
title: "Manual migration wizard",
value: true
},
{
name: "newtabinit",
factory: () => new NewTabInit(),
Expand All @@ -129,6 +118,20 @@ for (const {name, factory, title, value} of SECTION_FEEDS_CONFIG.concat([
title: "Preferences",
value: true
},
{
name: "section.topstories",
factory: () => new TopStoriesFeed(),
title: "Fetches content recommendations from a configurable content provider",
// Dynamically determine if Pocket should be shown for a geo / locale
getValue: ({geo, locale}) => {
const locales = ({
"US": ["en-US", "en-GB", "en-ZA"],
"CA": ["en-US", "en-GB", "en-ZA"],
"DE": ["de", "de-DE", "de-AT", "de-CH"]
})[geo];
return !!locales && locales.includes(locale);
}
},
{
name: "snippets",
factory: () => new SnippetsFeed(),
Expand All @@ -152,17 +155,14 @@ for (const {name, factory, title, value} of SECTION_FEEDS_CONFIG.concat([
factory: () => new TopSitesFeed(),
title: "Queries places and gets metadata for Top Sites section",
value: true
},
{
name: "migration",
factory: () => new ManualMigration(),
title: "Manual migration wizard",
value: true
}
])) {
const pref = `feeds.${name}`;
FEEDS_CONFIG.set(pref, factory);
PREFS_CONFIG.set(pref, {title, value});
];

const FEEDS_CONFIG = new Map();
for (const config of FEEDS_DATA) {
const pref = `feeds.${config.name}`;
FEEDS_CONFIG.set(pref, config.factory);
PREFS_CONFIG.set(pref, config);
}

this.ActivityStream = class ActivityStream {
Expand All @@ -183,15 +183,22 @@ this.ActivityStream = class ActivityStream {
this._defaultPrefs = new DefaultPrefs(PREFS_CONFIG);
}
init() {
this._updateDynamicPrefs();
this._defaultPrefs.init();

this.store.init(this.feeds);
this.store.dispatch({
type: at.INIT,
data: {version: this.options.version}
});

this.initialized = true;
}
uninit() {
if (this.geo === "") {
Services.prefs.removeObserver(GEO_PREF, this);
}

this.store.dispatch({type: at.UNINIT});
this.store.uninit();

Expand All @@ -205,7 +212,50 @@ this.ActivityStream = class ActivityStream {
this._defaultPrefs.reset();
}
}
_updateDynamicPrefs() {
// Save the geo pref if we have it
if (Services.prefs.prefHasUserValue(GEO_PREF)) {
this.geo = Services.prefs.getStringPref(GEO_PREF);
} else if (this.geo !== "") {
// Watch for geo changes and use a dummy value for now
Services.prefs.addObserver(GEO_PREF, this);
this.geo = "";
}

this.locale = Services.locale.getRequestedLocale();

// Update the pref config of those with dynamic values
for (const pref of PREFS_CONFIG.keys()) {
const prefConfig = PREFS_CONFIG.get(pref);
if (!prefConfig.getValue) {
continue;
}

const newValue = prefConfig.getValue({
geo: this.geo,
locale: this.locale
});

// If there's an existing value and it has changed, that means we need to
// overwrite the default with the new value.
if (prefConfig.value !== undefined && prefConfig.value !== newValue) {
this._defaultPrefs.setDefaultPref(pref, newValue);
}

prefConfig.value = newValue;
}
}
observe(subject, topic, data) {
switch (topic) {
case "nsPref:changed":
// We should only expect one geo change, so update and stop observing
if (data === GEO_PREF) {
this._updateDynamicPrefs();
Services.prefs.removeObserver(GEO_PREF, this);
}
break;
}
}
};

this.PREFS_CONFIG = PREFS_CONFIG;
this.EXPORTED_SYMBOLS = ["ActivityStream", "SECTIONS"];
this.EXPORTED_SYMBOLS = ["ActivityStream", "PREFS_CONFIG"];
6 changes: 3 additions & 3 deletions system-addon/lib/ActivityStreamPrefs.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ this.DefaultPrefs = class DefaultPrefs {
}

/**
* _setDefaultPref - Sets the default value (not user-defined) for a given pref
* setDefaultPref - Sets the default value (not user-defined) for a given pref
*
* @param {string} key The name of the pref
* @param {type} val The default value of the pref
*/
_setDefaultPref(key, val) {
setDefaultPref(key, val) {
switch (typeof val) {
case "boolean":
this.branch.setBoolPref(key, val);
Expand Down Expand Up @@ -89,7 +89,7 @@ this.DefaultPrefs = class DefaultPrefs {
} else {
value = prefConfig.value;
}
this._setDefaultPref(pref, value);
this.setDefaultPref(pref, value);
}
}

Expand Down
Loading