Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨Introduce varGroups for configRewriter. #20190

Merged
merged 10 commits into from
Jan 15, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions build-system/routes/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ router.post('/rewriter', (req, res) => {
extraUrlParams: {
testId: 12358,
rewritten: true,
reqBody: body,
},
};
Object.assign(body, extraUrlParams);
res.json(body);
const payload = Object.assign({}, body, extraUrlParams);
res.json(payload);
});

router.use('/:type', (req, res) => {
Expand Down
1 change: 1 addition & 0 deletions build-system/tasks/presubmit-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ const forbiddenTermsSrcInclusive = {
whitelist: [
'extensions/amp-form/0.1/amp-form.js',
'src/service/url-replacements-impl.js',
'extensions/amp-analytics/0.1/config.js',
'extensions/amp-analytics/0.1/cookie-writer.js',
'extensions/amp-analytics/0.1/requests.js',
],
Expand Down
53 changes: 53 additions & 0 deletions examples/analytics-varGroups.amp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<title>AMP Analytics varGroups</title>
<link rel="canonical" href="analytics-varGroups.amp.html" >
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<style amp-custom>
</style>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
</head>
<body>
<h1>AMP Analytics varGroups</h1>
<amp-analytics type="_fake_" id="_fake_" data-credentials="include">
<script type="application/json">
{
"configRewriter": {
"varGroups": {
"feature1": {
"enabled": true
},
"feature2": {
"enabled": true
}
}
},
"vars": {
"amp": true,
"id": 12345
}
}
</script>
</amp-analytics>

<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam pellentesque augue quis elementum tempus. Pellentesque sit amet neque bibendum, sagittis purus vitae, pellentesque magna. Vestibulum non viverra metus, eget feugiat lacus. Nulla in maximus orci. Maecenas id turpis vel ipsum vestibulum bibendum ut sit amet magna. Nullam hendrerit ex at est eleifend, nec dignissim nibh rutrum. Aliquam quis tellus et nibh faucibus laoreet in eget turpis. Nam quam nisl, porttitor vel ex eget, dapibus placerat dui. Mauris commodo pellentesque leo, eu tempus quam. In hac habitasse platea dictumst. Suspendisse non ante finibus, luctus augue non, luctus orci. Vestibulum ornare lacinia aliquam. In sollicitudin vehicula vulputate. Sed mi elit, commodo nec sapien nec, pretium bibendum leo. Donec id justo tortor. Ut in mauris dapibus, laoreet metus vitae, dictum nisi.
</p>
<p>
Integer dapibus egestas arcu. Nunc vitae velit congue, placerat augue quis, suscipit nisi. Donec suscipit imperdiet turpis pharetra feugiat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus aliquam eleifend dolor, at lacinia orci semper vel. Nunc semper sem vel tincidunt posuere. Nunc lobortis velit vitae condimentum mollis. Morbi eu ullamcorper mauris. Pellentesque ac eros maximus, pulvinar sapien vitae, semper nisi. Curabitur imperdiet non mauris vitae sollicitudin.
</p>
<p>
Nam posuere velit euismod risus pulvinar, in sollicitudin sapien consectetur. Vestibulum nec ex odio. Quisque at elit nec nunc ultricies lacinia nec non lorem. Maecenas porttitor consequat mauris, vitae porttitor ligula pellentesque ut. Pellentesque rhoncus diam vel lacus lobortis imperdiet. Sed maximus dictum hendrerit. Vivamus ornare, purus in laoreet sagittis, est ante pretium mauris, vel vulputate arcu erat eget mauris. Suspendisse eu lorem metus. Aliquam tempus aliquet urna, vitae mollis lacus pretium vitae. Etiam semper gravida commodo. Maecenas at pulvinar quam. Nullam dolor ipsum, ornare a sollicitudin et, sodales porttitor neque.
</p>
<p>
Integer in felis at lacus mattis facilisis. Curabitur tincidunt, felis porttitor mollis finibus, tortor elit elementum dolor, vel vulputate lorem dui id ante. Vivamus in velit at lectus blandit gravida vitae quis arcu. Nam et magna magna. Fusce condimentum diam lacus, ac ullamcorper purus malesuada eu. Mauris ullamcorper elit et venenatis faucibus. Nullam lobortis molestie purus quis pellentesque. Sed at libero id nisi rhoncus tincidunt. Praesent vestibulum vehicula tristique. Etiam rutrum, nunc id porta interdum, nulla nisi molestie leo, at fermentum justo dolor at lorem. Duis in egestas sapien.
</p>
<p>
Donec pharetra molestie sollicitudin. Duis mattis eleifend rutrum. Quisque luctus tincidunt lacus, vitae lobortis nisi malesuada ac. Aliquam mattis leo vel elit rutrum, nec consequat massa vestibulum. Maecenas bibendum metus nec ante feugiat, eu faucibus orci mattis. Cras tristique sem non elit congue malesuada. Proin ornare, lacus et porttitor consequat, sapien urna rutrum diam, ac pellentesque ligula est eget nisi. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec ultrices sollicitudin eros a placerat. Proin eget pulvinar est. Donec posuere ultrices odio at ultrices. Suspendisse potenti. Phasellus id orci id purus porttitor consectetur a at erat. Nullam volutpat ultricies nisl id maximus. Morbi porta ex ante, et egestas odio ultricies consequat.
</p>
</body>
</html>
199 changes: 148 additions & 51 deletions extensions/amp-analytics/0.1/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import {ANALYTICS_CONFIG} from './vendors';
import {Services} from '../../../src/services';
import {assertHttpsUrl} from '../../../src/url';
import {deepMerge, dict, hasOwn} from '../../../src/utils/object';
import {dev, user, userAssert} from '../../../src/log';
import {dict, hasOwn} from '../../../src/utils/object';
import {getChildJsonConfig} from '../../../src/json';
import {getMode} from '../../../src/mode';
import {isArray, isObject, toWin} from '../../../src/types';
Expand Down Expand Up @@ -70,56 +70,6 @@ export class AnalyticsConfig {
.then(() => this.config_);
}

/**
* Returns a promise that resolves when configuration is re-written if
* configRewriter is configured by a vendor.
*
* @private
* @return {!Promise<undefined>}
*/
processConfigs_() {
const configRewriterUrl = this.getConfigRewriter_()['url'];

const config = dict({});
const inlineConfig = this.getInlineConfigNoInline();
this.validateTransport_(inlineConfig);
mergeObjects(inlineConfig, config);
mergeObjects(this.remoteConfig_, config);

if (!configRewriterUrl || this.isSandbox_) {
this.config_ = this.mergeConfigs_(config);
// use default configuration merge.
return Promise.resolve();
}

assertHttpsUrl(configRewriterUrl, this.element_);
const TAG = this.getName_();
dev().fine(TAG, 'Rewriting config', configRewriterUrl);

const fetchConfig = {
method: 'POST',
body: config,
requireAmpResponseSourceOrigin: false,
};
if (this.element_.hasAttribute('data-credentials')) {
fetchConfig.credentials = this.element_.getAttribute('data-credentials');
}
return Services.urlReplacementsForDoc(this.element_)
.expandUrlAsync(configRewriterUrl)
.then(expandedUrl => {
return Services.xhrFor(toWin(this.win_)).fetchJson(
expandedUrl, fetchConfig);
})
.then(res => res.json())
.then(jsonValue => {
this.config_ = this.mergeConfigs_(jsonValue);
dev().fine(TAG, 'Configuration re-written', configRewriterUrl);
}, err => {
user().error(TAG,
'Error rewriting configuration: ', configRewriterUrl, err);
});
}

/**
* Returns a promise that resolves when remote config is ready (or
* immediately if no remote config is specified.)
Expand Down Expand Up @@ -157,6 +107,127 @@ export class AnalyticsConfig {
});
}

/**
* Returns a promise that resolves when configuration is re-written if
* configRewriter is configured by a vendor.
* @private
* @return {!Promise<undefined>}
*/
processConfigs_() {
const configRewriterUrl = this.getConfigRewriter_()['url'];

const config = dict({});
const inlineConfig = this.getInlineConfigNoInline();
this.validateTransport_(inlineConfig);
mergeObjects(inlineConfig, config);
mergeObjects(this.remoteConfig_, config);

if (!configRewriterUrl || this.isSandbox_) {
this.config_ = this.mergeConfigs_(config);
// use default configuration merge.
return Promise.resolve();
}

return this.handleConfigRewriter_(config, configRewriterUrl);
}

/**
* Handles logic if configRewriter is enabled.
* @param {!JsonObject} config
* @param {string} configRewriterUrl
*/
handleConfigRewriter_(config, configRewriterUrl) {
assertHttpsUrl(configRewriterUrl, this.element_);
const TAG = this.getName_();
dev().fine(TAG, 'Rewriting config', configRewriterUrl);

return this.handleVarGroups_(config).then(() => {
const fetchConfig = {
method: 'POST',
body: config,
requireAmpResponseSourceOrigin: false,
};
if (this.element_.hasAttribute('data-credentials')) {
fetchConfig.credentials = this.element_
.getAttribute('data-credentials');
}
return Services.urlReplacementsForDoc(this.element_)
.expandUrlAsync(configRewriterUrl)
.then(expandedUrl => {
return Services.xhrFor(toWin(this.win_)).fetchJson(
expandedUrl, fetchConfig);
})
.then(res => res.json())
.then(jsonValue => {
this.config_ = this.mergeConfigs_(jsonValue);
dev().fine(TAG, 'Configuration re-written', configRewriterUrl);
}, err => {
user().error(TAG,
'Error rewriting configuration: ', configRewriterUrl, err);
});
});
}

/**
* Check to see which varGroups are enabled, resolve and merge them into
* vars object.
* @param {!JsonObject} pubConfig
* @return {!Promise}
*/
handleVarGroups_(pubConfig) {
const pubRewriterConfig = pubConfig['configRewriter'];
const pubVarGroups = pubRewriterConfig && pubRewriterConfig['varGroups'];
const vendorVarGroups = this.getConfigRewriter_()['varGroups'];

if (!pubVarGroups && !vendorVarGroups) {
return Promise.resolve();
}

if (pubVarGroups && !vendorVarGroups) {
const TAG = this.getName_();
user().warn(TAG, 'This analytics provider does not currently ' +
'support varGroups');
return Promise.resolve();
}

// Create object that will later hold all the resolved variables, and any
// intermediary objects as necessary.
pubConfig['configRewriter'] = pubConfig['configRewriter'] || dict();
const rewriterConfig = pubConfig['configRewriter'];
rewriterConfig['vars'] = dict({});

const allPromises = [];
// Merge publisher && vendor varGroups to see what has been enabled.
const mergedConfig = pubVarGroups || dict();
deepMerge(mergedConfig, vendorVarGroups);

Object.keys(mergedConfig).forEach(groupName => {
const group = mergedConfig[groupName];
if (!group['enabled']) {
// Any varGroups must be explicitly enabled.
return;
}

const groupPromise = shallowExpandObject(this.element_, group)
.then(expandedGroup => {
// This is part of the user config and should not be sent.
delete expandedGroup['enabled'];
// Merge all groups into single `vars` object.
Object.assign(rewriterConfig['vars'], expandedGroup);
});
allPromises.push(groupPromise);
});

return Promise.all(allPromises).then(() => {
// Don't send an empty vars payload.
if (!Object.keys(rewriterConfig['vars']).length) {
return delete pubConfig['configRewriter'];
}
// Don't send varGroups in payload to configRewriter endpoint.
pubVarGroups && delete rewriterConfig['varGroups'];
});
}

/**
* Merges various sources of configs and stores them in a member variable.
*
Expand Down Expand Up @@ -347,3 +418,29 @@ function expandRequestStr(request) {
'baseUrl': request,
};
}

/**
* Expands all key value pairs asynchronously and returns a promise that will
* resolve with the expanded object.
* @param {!Element|!ShadowRoot} element
* @param {!Object} obj
* @return {!Promise<Object>}
*/
function shallowExpandObject(element, obj) {
const expandedObj = dict();
const keys = [];
const expansionPromises = [];

Object.keys(obj).forEach(key => {
keys.push(key);
const expanded = Services.urlReplacementsForDoc(element)
.expandStringAsync(obj[key]);
expansionPromises.push(expanded);
});

return Promise.all(expansionPromises).then(expandedValues => {
keys.forEach((key, i) =>
expandedObj[key] = expandedValues[i]);
return expandedObj;
});
}