Skip to content

Commit

Permalink
Add remote config loading support to amp-analytics.
Browse files Browse the repository at this point in the history
  • Loading branch information
btownsend committed Dec 18, 2015
1 parent da89bab commit 91a9b05
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 34 deletions.
2 changes: 2 additions & 0 deletions examples/analytics.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
</script>
</amp-analytics>

<amp-analytics id="analytics3" config="./analytics.config.json"></amp-analytics>

<div class="logo"></div>
<h1 id="top">AMP Analytics</h1>

Expand Down
15 changes: 15 additions & 0 deletions examples/analytics.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"requests": {
"event": "https://example.com?remote-test&title=${title}&r=${random}"
},
"vars": {
"title": "Example Request"
},
"triggers": {
"remote pageview": {
"on": "visible",
"request": "event"
}
}
}

96 changes: 62 additions & 34 deletions extensions/amp-analytics/0.1/amp-analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
* limitations under the License.
*/

import {assertHttpsUrl} from '../../../src/url';
import {isExperimentOn} from '../../../src/experiments';
import {installCidService} from '../../../src/service/cid-impl';
import {Layout} from '../../../src/layout';
import {log} from '../../../src/log';
import {urlReplacementsFor} from '../../../src/url-replacements';
import {expandTemplate} from '../../../src/string';
import {xhrFor} from '../../../src/xhr';

import {addListener} from './instrumentation';
import {sendRequest} from './transport';
Expand Down Expand Up @@ -68,10 +70,7 @@ export class AmpAnalytics extends AMP.BaseElement {
return Promise.resolve();
}

/**
* @private {!JSONObject} The analytics config associated with the tag
*/
this.config_ = this.mergeConfigs_();
this.element.setAttribute('aria-hidden', 'true');

/**
* @private {?string} Predefinedtype associated with the tag. If specified,
Expand All @@ -84,36 +83,68 @@ export class AmpAnalytics extends AMP.BaseElement {
* format string used by the tag to send data
*/
this.requests_ = {};
this.element.setAttribute('aria-hidden', 'true');

if (this.hasOptedOut_()) {
// Nothing to do when the user has opted out.
log.fine(this.getName_(), 'User has opted out. No hits will be sent.');
return Promise.resolve();
}
/**
* @private {JSONObject}
*/
this.remoteConfig = {};

return this.fetchRemoteConfig_().then(() => {
/**
* @private {!JSONObject} The analytics config associated with the tag
*/
this.config_ = this.mergeConfigs_();

if (this.hasOptedOut_()) {
// Nothing to do when the user has opted out.
log.fine(this.getName_(), 'User has opted out. No hits will be sent.');
return Promise.resolve();
}

this.generateRequests_();
this.generateRequests_();

if (!this.config_['triggers']) {
log.error(this.getName_(), 'No triggers were found in the config. No ' +
'analytics data will be sent.');
return Promise.resolve();
}
if (!this.config_['triggers']) {
log.error(this.getName_(), 'No triggers were found in the config. No ' +
'analytics data will be sent.');
return Promise.resolve();
}

// Trigger callback can be synchronous. Do the registration at the end.
for (const k in this.config_['triggers']) {
if (this.config_['triggers'].hasOwnProperty(k)) {
const trigger = this.config_['triggers'][k];
if (!trigger['on'] || !trigger['request']) {
log.warn(this.getName_(), '"on" and "request" attributes are ' +
'required for data to be collected.');
continue;
// Trigger callback can be synchronous. Do the registration at the end.
for (const k in this.config_['triggers']) {
if (this.config_['triggers'].hasOwnProperty(k)) {
const trigger = this.config_['triggers'][k];
if (!trigger['on'] || !trigger['request']) {
log.warn(this.getName_(), '"on" and "request" attributes are ' +
'required for data to be collected.');
continue;
}
addListener(this.getWin(), trigger['on'],
this.handleEvent_.bind(this, trigger), trigger['selector']);
}
addListener(this.getWin(), trigger['on'],
this.handleEvent_.bind(this, trigger), trigger['selector']);
}
});
}

/**
* Returns a promise that resolves when remote config is ready (or
* immediately if no remote config is specified.)
* @private
* @return {!Promise<>}
*/
fetchRemoteConfig_() {
const remoteConfigUrl = this.element.getAttribute('config');
if (!remoteConfigUrl) {
return Promise.resolve();
}
return Promise.resolve();
assertHttpsUrl(remoteConfigUrl);
log.fine(this.getName_(), 'Fetching remote config', remoteConfigUrl);
return xhrFor(this.getWin()).fetchJson(remoteConfigUrl).then(jsonValue => {
this.remoteConfig_ = jsonValue;
log.fine(this.getName_(), 'Remote config loaded', remoteConfigUrl);
}, err => {
log.warn(this.getName_(), 'Error loading remote config',
remoteConfigUrl, err);
});
}

/**
Expand All @@ -125,12 +156,10 @@ export class AmpAnalytics extends AMP.BaseElement {
* - Predefined config: Defined as part of the platform.
* - Default config: Built-in config shared by all amp-analytics tags.
*
* @return {!JSONObject} the merged config.
* @private
* @return {!JSONObject}
*/
mergeConfigs_() {
// TODO(btownsend, #871): Implement support for remote configuration.
const remoteConfig = {};
let inlineConfig = {};
try {
const children = this.element.children;
Expand Down Expand Up @@ -163,7 +192,7 @@ export class AmpAnalytics extends AMP.BaseElement {
this.mergeObjects_(defaultConfig, config);
this.mergeObjects_(typeConfig, config);
this.mergeObjects_(inlineConfig, config);
this.mergeObjects_(remoteConfig, config);
this.mergeObjects_(this.remoteConfig_, config);
return config;
}

Expand Down Expand Up @@ -287,7 +316,7 @@ export class AmpAnalytics extends AMP.BaseElement {
};

if (to === null || to === undefined) {
return from;
to = {};
}

for (const property in from) {
Expand All @@ -302,8 +331,7 @@ export class AmpAnalytics extends AMP.BaseElement {
if (!isObject(to[property])) {
to[property] = {};
}
to[property] = this.mergeObjects_(from[property],
to[property]);
to[property] = this.mergeObjects_(from[property], to[property]);
} else {
to[property] = from[property];
}
Expand Down
22 changes: 22 additions & 0 deletions extensions/amp-analytics/0.1/test/test-amp-analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import {AmpAnalytics} from '../../../../build/all/v0/amp-analytics-0.1.max';
import {adopt} from '../../../../src/runtime';
import {getService} from '../../../../src/service';
import {markElementScheduledForTesting} from '../../../../src/service';
import {installCidService} from '../../../../src/service/cid-impl';
import * as sinon from 'sinon';
Expand All @@ -27,6 +28,11 @@ describe('amp-analytics', function() {
let sandbox;
let windowApi;
let sendRequestSpy;
let fetchJsonResponse;

const jsonMockResponses = {
'config1': '{"vars": {"title": "remote"}}'
};

beforeEach(() => {
markElementScheduledForTesting(window, 'amp-analytics');
Expand All @@ -42,6 +48,9 @@ describe('amp-analytics', function() {
windowApi.Object = window.Object;
markElementScheduledForTesting(windowApi, 'amp-analytics');
installCidService(windowApi);
getService(windowApi, 'xhr', () => {return {
fetchJson: url => Promise.resolve(JSON.parse(jsonMockResponses[url]))
};});
});

afterEach(() => {
Expand Down Expand Up @@ -464,4 +473,17 @@ describe('amp-analytics', function() {
});
});
});

it('fetches and merges remote config', () => {
const analytics = getAnalyticsTag({
'vars': {'title': 'local'},
'requests': {'foo': 'https://example.com/${title}'},
'triggers': [{'on': 'visible', 'request': 'foo'}]
}, {
'config': 'config1'
});
return analytics.layoutCallback().then(() => {
expect(sendRequestSpy.args[0][0]).to.equal('https://example.com/remote');
});
});
});

0 comments on commit 91a9b05

Please sign in to comment.