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

Updates amp-analytics with support for expanding config/trigger vars. #1102

Merged
merged 1 commit into from
Dec 10, 2015
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
101 changes: 38 additions & 63 deletions examples/analytics.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,53 @@
<html ⚡>
<head>
<meta charset="utf-8">
<title>AMP #0</title>
<link rel="canonical" href="amps.html" >
<title>AMP Analytics</title>
<link rel="canonical" href="analytics.amp.html" >
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<link href='https://fonts.googleapis.com/css?family=Questrial' rel='stylesheet' type='text/css'>
<style amp-custom>
amp-img {
max-width: 80%;
max-height: 80%;
width: 100%;
}
</style>
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
<style>body {opacity: 0}</style><noscript><style>body {opacity: 1}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>

<article>
<amp-analytics id="analytics1">
{
"host": "my-analytics.com",
"requests": {
"default": "/log?domain=DOMAIN&amp;path=PATH"
},
"triggers": [{
"on": "visible",
"request": "default"
}]
}
</amp-analytics>
<amp-analytics type="googleanalytics" id="analytics2">
{
<amp-analytics id="analytics1">
{
"host": "example.com",
"requests": {
"base": "/log?domain=DOMAIN&amp;path=PATH&amp;title=${title}",
"event": "${base}&amp;name=${eventName}&amp;type=${eventId}"
},
"vars": {
"title": "Example Request"
},
"triggers": [{
"on": "visible",
"request": "event",
"vars": {
"account": "UA-123456-1",
"anonymize_ip": true
},
"triggers": [{
"on": "visible",
"request": "pageview"
}]
}
</amp-analytics>
<div class="logo"></div>

<h1 id="top">AMP #0</h1>
<p>
<amp-fit-text width="300" height="200" layout="responsive" class="box1">
Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt.
</amp-fit-text>
</p>
<p>
<div class="box1">
Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt.
</div>
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse semper aliquet turpis ac fringilla. Cras dolor nisi, malesuada eu semper non, faucibus sed nisl. Quisque urna velit, accumsan non ante ac, fermentum dignissim justo. Etiam libero massa, commodo a dignissim eu, fringilla eu ante. Nam neque diam, tincidunt in rhoncus a, luctus ac eros. Integer tellus mauris, convallis quis mauris et, tincidunt venenatis lorem. Maecenas vulputate pulvinar quam. Phasellus nec felis ultrices, faucibus dui eu, sodales arcu. Nullam pellentesque erat at lorem volutpat vehicula. Phasellus id lectus risus. Proin consectetur dapibus nulla eget viverra. Donec a scelerisque metus. Morbi vitae diam tempor, facilisis risus id, cursus erat.

Maecenas tellus dolor, euismod eget porta eu, pretium in diam. Nulla fermentum iaculis leo et ornare. Mauris euismod, risus vitae auctor lacinia, massa elit eleifend neque, sit amet tincidunt nisi diam quis quam. Donec id vestibulum leo, tempor lobortis erat. Nunc eget porta eros. Proin mi leo, iaculis vitae dui at, euismod auctor ante. Integer ut mi nisl. Morbi ut ipsum vel quam fermentum maximus. Morbi aliquet massa quis justo malesuada, sit amet tempus est tempor. Nulla tincidunt orci odio, sed porta nisl fermentum quis. Etiam rhoncus vel eros ut mollis. Nulla vitae risus vel diam porta rutrum vitae a mi. Curabitur id bibendum mi. Morbi sagittis id dui eu suscipit. Pellentesque porttitor placerat nisi non volutpat.

Curabitur mattis libero a venenatis porta. Integer ac aliquam nulla. Nunc non orci sit amet metus consectetur fermentum. Suspendisse et velit sit amet nulla rhoncus semper. Vivamus et dolor tempus, ultricies metus at, lacinia nisi. Nunc justo dui, efficitur ut orci eget, vehicula feugiat est. Suspendisse non urna congue, feugiat est nec, tempus nisi. In convallis vitae lectus eu finibus. Vestibulum ullamcorper id turpis id pulvinar. Nulla facilisi. Aenean purus odio, imperdiet sed nisl ut, luctus ullamcorper leo. Aenean sit amet pharetra nunc. Integer maximus diam vitae tortor lobortis, sed facilisis ante vehicula. Mauris rutrum pharetra pretium. Praesent laoreet faucibus odio, quis fringilla libero convallis quis.

Suspendisse viverra augue nec elementum ornare. Nulla vel dui at leo pharetra dapibus non nec risus. Vestibulum hendrerit leo felis, vel accumsan mi molestie vel. Aenean nisi quam, iaculis eu erat euismod, tristique euismod magna. Maecenas sem ante, consectetur et magna ut, pulvinar euismod arcu. Nullam eget mollis dolor, ac tristique ex. Nulla at elit pretium elit aliquam facilisis nec ut sapien. Duis et sem dapibus, pharetra risus sit amet, vulputate libero. Fusce commodo tellus sit amet arcu egestas varius. Integer nunc nisl, maximus tincidunt magna sed, aliquam molestie lectus. Suspendisse mollis ac diam et ornare. Nam ut suscipit odio. Integer arcu tellus, sagittis in tempus gravida, elementum sit amet nulla. Sed non nisl odio.
"eventName": "page-loaded",
"eventId": "42"
}
}]
}
</amp-analytics>

Nunc a ipsum nisi. Proin sodales vestibulum nisl, non viverra ipsum efficitur eu. Sed vitae mi congue, vestibulum diam nec, semper odio. Nulla nec luctus mauris. Pellentesque ornare nulla at mi tempor bibendum. Cras quis viverra nunc. Nulla condimentum neque a dictum ullamcorper.
<amp-analytics type="googleanalytics" id="analytics2">
{
"vars": {
"account": "UA-XXXX-Y"
},
"triggers": [{
"on": "visible",
"request": "pageview",
"vars" : {
"title": "Example Pageview"
}
}]
}
</amp-analytics>

</p>
<p>
<amp-img id="img1" src="https://lh3.googleusercontent.com/pSECrJ82R7-AqeBCOEPGPM9iG9OEIQ_QXcbubWIOdkY=w400-h300-no-n" width=800 height=600></amp-img>
<amp-img src="https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-Rf78IofLb9QjS5_0mqsY1zEFc=w400-h300-no-n" width=400 height=300 layout=responsive></amp-img>
<amp-img src="https://lh3.googleusercontent.com/Z4gtm5Bkxyv21Z2PtbTf95Clb9AE4VTR6olbBKYrenM=w400-h300-no-n" width=400 height=300></amp-img>
</p>
</article>
<div class="logo"></div>
<h1 id="top">AMP Analytics</h1>
</body>
</html>

50 changes: 19 additions & 31 deletions extensions/amp-analytics/0.1/amp-analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {Layout} from '../../../src/layout';
import {log} from '../../../src/log';
import {loadPromise} from '../../../src/event-helper';
import {urlReplacementsFor} from '../../../src/url-replacements';
import {expandTemplate} from '../../../src/string';

import {addListener} from './instrumentation';
import {ANALYTICS_CONFIG} from './vendors';
Expand Down Expand Up @@ -132,6 +133,7 @@ export class AmpAnalytics extends AMP.BaseElement {
}
const config = this.predefinedConfig_[this.element.getAttribute('type')]
|| {};

return this.mergeObjects_(remote, this.mergeObjects_(inline, config));
}

Expand Down Expand Up @@ -168,45 +170,20 @@ export class AmpAnalytics extends AMP.BaseElement {
'will not be sent from this page.');
return;
}

for (const k in this.config_['requests']) {
if (this.config_['requests'].hasOwnProperty(k)) {
requests[k] = this.config_['requests'][k];
}
}
this.requests_ = requests;

// Now replace any request templates with their values.
const all = Object.keys(this.requests_).join('|');
const requestExpr = new RegExp('\{(' + all + ')\}', 'g');
// Expand any placeholders. For requests, we expand each string up to 5
// times to support nested requests. Leave any unresolved placeholders.
for (const k in this.requests_) {
this.requests_[k] = this.expandRequest_(this.requests_[k], requestExpr,
this.requests_, 5);
}
}

/**
* Replaces template variables corresponding to a request with the value of
* that request. If no request is found, an empty string is inserted instead.
*
* @param {string} formatString The request string to be expanded
* @param {!RegExp} expression The regular expression used to detect request
* templates
* @param {!Object<string, string> requests Map of requests to lookup template
* values from
* @param {number} depth Depth of recursion for nested templates
* @return {string} the expanded request string
* @private
*/
expandRequest_(formatString, expression, requests, depth) {
if (depth <= 0) {
return '';
this.requests_[k] = expandTemplate(this.requests_[k], key => {
return this.requests_[key] || '${' + key + '}';
}, 5);
}
depth--;
return formatString.replace(expression, (match, name) => {
return this.expandRequest_(requests[name], expression, requests, depth) ||
'';
});
}

/**
Expand All @@ -224,7 +201,18 @@ export class AmpAnalytics extends AMP.BaseElement {
return;
}

// TODO(btownsend, #871): Add support for variables from inline config.
// Replace placeholders with URI encoded values.
// Precedence is trigger.vars > config.vars > built-in.vars.
// Nested expansion not supported.
// TODO(btownsend, #1116) Add support for built-in vars.
request = expandTemplate(request, key => {
return encodeURIComponent(
(trigger['vars'] && trigger['vars'][key]) ||
(this.config_['vars'] && this.config_['vars'][key]) ||
'');
});

// For consistentcy with amp-pixel we also expand any url replacements.
request = urlReplacementsFor(this.getWin()).expand(request);

// TODO(btownsend, #1061): Add support for sendBeacon.
Expand Down
133 changes: 126 additions & 7 deletions extensions/amp-analytics/0.1/test/test-amp-analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ describe('amp-analytics', function() {
const WindowApi = function() {};
windowApi = new WindowApi();
windowApi.location = {hash: '', href: '/test/viewer'};
windowApi.document = {
createElement: document.createElement,
title: 'Test Title',
referrer: 'https://www.google.com/'
};
windowApi.Object = window.Object;
windowApi.document = document;
});

afterEach(() => {
Expand Down Expand Up @@ -114,32 +118,32 @@ describe('amp-analytics', function() {
it('expands nested requests', function() {
const analytics = getAnalyticsTag({
'host': 'example.com',
'requests': {'foo': '/bar&{foobar}&baz', 'foobar': 'foobar'},
'requests': {'foo': '/bar&${foobar}&baz', 'foobar': 'f1'},
'triggers': [{'on': 'visible', 'request': 'foo'}]
});

analytics.buildCallback();
expect(sendRequestSpy.calledOnce).to.be.true;
expect(sendRequestSpy.args[0][0])
.to.equal('https://example.com/bar&foobar&baz');
.to.equal('https://example.com/bar&f1&baz');
});

it('expands nested requests', function() {
const analytics = getAnalyticsTag({
'host': 'example.com',
'requests': {'foo': '/bar&{foobar}', 'foobar': '{baz}', 'baz': 'baz'},
'requests': {'foo': '/bar&${foobar}', 'foobar': '${baz}', 'baz': 'b1'},
'triggers': [{'on': 'visible', 'request': 'foo'}]
});

analytics.buildCallback();
expect(sendRequestSpy.calledOnce).to.be.true;
expect(sendRequestSpy.args[0][0]).to.equal('https://example.com/bar&baz');
expect(sendRequestSpy.args[0][0]).to.equal('https://example.com/bar&b1');
});

it('expands recursive requests', function() {
const analytics = getAnalyticsTag({
'host': 'example.com',
'requests': {'foo': '/bar&{foobar}&baz', 'foobar': '{foo}'},
'requests': {'foo': '/bar&${foobar}&baz', 'foobar': '${foo}'},
'triggers': [{'on': 'visible', 'request': 'foo'}]
});

Expand Down Expand Up @@ -180,7 +184,7 @@ describe('amp-analytics', function() {
it('merges requests correctly', function() {
const analytics = getAnalyticsTag({
'host': 'example.com',
'requests': {'foo': '/{bar}'},
'requests': {'foo': '/${bar}'},
'triggers': [{'on': 'visible', 'request': 'foo'}]
}, {'type': 'xyz'});

Expand Down Expand Up @@ -229,4 +233,119 @@ describe('amp-analytics', function() {
'foobar': {'foobar': ['abc', 'def']}
});
});

it('expands trigger vars', () => {
const analytics = getAnalyticsTag({
'host': 'example.com',
'requests': {'pageview': '/test1=${var1}&test2=${var2}'},
'triggers': [{
'on': 'visible',
'request': 'pageview',
'vars': {
'var1': 'x',
'var2': 'test2'
}
}]});
analytics.buildCallback();
expect(sendRequestSpy.calledOnce).to.be.true;
expect(sendRequestSpy.args[0][0]).to.equal(
'https://example.com/test1=x&test2=test2');
});

it('expands config vars', () => {
const analytics = getAnalyticsTag({
'host': 'example.com',
'vars': {
'var1': 'x',
'var2': 'test2'
},
'requests': {'pageview': '/test1=${var1}&test2=${var2}'},
'triggers': [{'on': 'visible', 'request': 'pageview'}]});
analytics.buildCallback();
expect(sendRequestSpy.calledOnce).to.be.true;
expect(sendRequestSpy.args[0][0]).to.equal(
'https://example.com/test1=x&test2=test2');
});

it('expands trigger vars with higher precedence than config vars', () => {
const analytics = getAnalyticsTag({
'host': 'example.com',
'vars': {
'var1': 'config1',
'var2': 'config2'
},
'requests': {'pageview': '/test1=${var1}&test2=${var2}'},
'triggers': [{
'on': 'visible',
'request': 'pageview',
'vars': {
'var1': 'trigger1'
}}]});
analytics.buildCallback();
expect(sendRequestSpy.calledOnce).to.be.true;
expect(sendRequestSpy.args[0][0]).to.equal(
'https://example.com/test1=trigger1&test2=config2');
});

it('does not expand nested vars', () => {
const analytics = getAnalyticsTag({
'host': 'example.com',
'requests': {'pageview': '/test=${var1}'},
'triggers': [{
'on': 'visible',
'request': 'pageview',
'vars': {
'var1': '${var2}',
'var2': 't2'
}}]});
analytics.buildCallback();
expect(sendRequestSpy.calledOnce).to.be.true;
expect(sendRequestSpy.args[0][0]).to.equal(
'https://example.com/test=%24%7Bvar2%7D');
});

it('expands and encodes requests, config vars, and trigger vars', () => {
const analytics = getAnalyticsTag({
'host': 'example.com',
'vars': {
'c1': 'config 1',
'c2': 'config&2'
},
'requests': {
'base': '/test?c1=${c1}&t1=${t1}',
'pageview': '${base}&c2=${c2}&t2=${t2}'
},
'triggers': [{
'on': 'visible',
'request': 'pageview',
'vars': {
't1': 'trigger=1',
't2': 'trigger?2'
}}]});
analytics.buildCallback();
expect(sendRequestSpy.calledOnce).to.be.true;
expect(sendRequestSpy.args[0][0]).to.equal(
'https://example.com/test?c1=config%201&t1=trigger%3D1&' +
'c2=config%262&t2=trigger%3F2');
});

it('expands url-replacements vars', () => {
const analytics = getAnalyticsTag({
'host': 'example.com',
'requests': {'pageview': '/test1=${var1}&test2=${var2}&title=TITLE'},
'triggers': [{
'on': 'visible',
'request': 'pageview',
'vars': {
'var1': 'x',
'var2': 'DOCUMENT_REFERRER'
}
}]});
analytics.buildCallback();
expect(sendRequestSpy.calledOnce).to.be.true;
expect(sendRequestSpy.args[0][0]).to.equal(
'https://example.com/test1=x&test2=https%3A%2F%2Fwww.google.com%2F' +
'&title=Test%20Title');
});

});
Loading