Skip to content

Commit

Permalink
✨ amp-analytics Nested Value Template Expansion (#19609)
Browse files Browse the repository at this point in the history
* Support nested URL param template expansion

* Modify useBody integration test for nested objects and arrays

* Document useBody

* Use isObject

* Add links to "Use Body for Extra URL Params" section from "Transport"

* Add more levels of objects/arrays in useBody integration test

* Rename parent variable to params

* Add test for useBody with extraUrlParamsReplaceMap

* Fix integration test undefined var
  • Loading branch information
ryanashcraft authored and calebcordry committed Dec 10, 2018
1 parent 5d7bfac commit a295a03
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 14 deletions.
30 changes: 21 additions & 9 deletions extensions/amp-analytics/0.1/requests.js
Expand Up @@ -24,7 +24,7 @@ import {Services} from '../../../src/services';
import {dev, user} from '../../../src/log';
import {dict} from '../../../src/utils/object';
import {getResourceTiming} from './resource-timing';
import {isArray, isFiniteNumber} from '../../../src/types';
import {isArray, isFiniteNumber, isObject} from '../../../src/types';

const BATCH_INTERVAL_MIN = 200;

Expand Down Expand Up @@ -355,15 +355,27 @@ function expandExtraUrlParams(variableService, urlReplacements, params,
expansionOption.vars,
expansionOption.iterations,
true /* noEncode */);
// Add any given extraUrlParams as query string param
for (const k in params) {
if (typeof params[k] == 'string') {
const request = variableService.expandTemplate(params[k], option)
.then(v =>
urlReplacements.expandStringAsync(v, bindings, opt_whitelist))
.then(value => params[k] = value);

const expandObject = (params, key) => {
const value = params[key];

if (typeof value === 'string') {
const request = variableService.expandTemplate(value, option)
.then(value =>
urlReplacements.expandStringAsync(
value, bindings, opt_whitelist))
.then(value => params[key] = value);
requestPromises.push(request);
} else if (isArray(value)) {
value.forEach((_, index) => expandObject(value, index));
} else if (isObject(value) && value !== null) {
Object.keys(value).forEach(key => expandObject(value, key));
}
}
};

Object.keys(params).forEach(key =>
expandObject(params, key)
);

return Promise.all(requestPromises).then(() => params);
}
27 changes: 24 additions & 3 deletions extensions/amp-analytics/amp-analytics.md
Expand Up @@ -267,7 +267,7 @@ The `vars` configuration object can be used to define new key-value pairs or ove

#### Extra URL Params

The `extraUrlParams` configuration object specifies additional parameters to append to the query string of a request URL via the usual "&foo=baz" convention.
The `extraUrlParams` configuration object specifies additional parameters to be included in the request. By default, extra URL params are appended to the query string of a request URL via the usual "&foo=baz" convention.

Here's an example that would append `&a=1&b=2&c=3` to a request:

Expand All @@ -279,10 +279,14 @@ Here's an example that would append `&a=1&b=2&c=3` to a request:
}
```

`extraUrlParams` may be sent via the request body instead of the URL if `useBody` is enabled and the request is sent via the `beacon` or `xhrpost` transport methods. In this case, the parameters are not URL encoded or flattened. See [Use Body for Extra URL Params](#use-body-for-extra-url-params) for more details.

The `extraUrlParamsReplaceMap` attribute specifies a map of keys and values that act as parameters to `String.replace()` to pre-process keys in the `extraUrlParams` configuration. For example, if an `extraUrlParams` configuration defines `"page.title": "The title of my page"` and the `extraUrlParamsReplaceMap` defines `"page.": "_p_"`, then `&_p_title=The%20title%20of%20my%20page%20` will be appended to the request.

`extraUrlParamsReplaceMap` is not required to use `extraUrlParams`. If `extraUrlParamsReplaceMap` is not defined, then no string substitution will happens and the strings defined in `extraUrlParams` are used as-is.

If `useBody` is enabled and the request is sent via the `beacon` or `xhrpost` transport methods, `extraUrlParamsReplaceMap` string substitution will only be performed on the top-level keys in `extraUrlParams`.

#### Triggers

The `triggers` configuration object describes when an analytics request should be sent. The `triggers` attribute contains a key-value pair of trigger-name and trigger-configuration. A trigger-name can be any string comprised of alphanumeric characters (a-zA-Z0-9). Triggers from a configuration with lower precedence are overridden by triggers with the same names from a configuration with higher precedence.
Expand Down Expand Up @@ -653,8 +657,8 @@ Video analytics provides several triggers (`"on": "video-*"`) that publishers ca
The `transport` configuration object specifies how to send a request. The value is an object with fields that
indicate which transport methods are acceptable.

- `beacon` Indicates [`navigator.sendBeacon`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) can be used to transmit the request. This will send a POST request, with credentials, and an empty body.
- `xhrpost` Indicates `XMLHttpRequest` can be used to transmit the request. This will send a POST request, with credentials, and an empty body.
- `beacon` Indicates [`navigator.sendBeacon`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) can be used to transmit the request. This will send a POST request with credentials. The request will be sent with an empty body unless `useBody` is true. See [Use Body for Extra URL Params](#use-body-for-extra-url-params) for more information about `useBody`.
- `xhrpost` Indicates `XMLHttpRequest` can be used to transmit the request. This will send a POST request with credentials. The request will be sent with an empty body unless `useBody` is true. See [Use Body for Extra URL Params](#use-body-for-extra-url-params) for more information about `useBody`.
- `image` Indicates the request can be sent by generating an `Image` tag. This will send a GET request. To suppress console warnings due to empty responses or request failures, set `"image": {"suppressWarnings": true}`.

MRC-accredited vendors may utilize a fourth transport mechanism, "iframe transport", by adding a URL string to iframe-transport-vendors.js. This indicates that an iframe should be created, with its `src` attribute set to this URL, and requests will be sent to that iframe via `window.postMessage()`. In this case, requests need not be full-fledged URLs. `iframe` may only be specified in `iframe-transport-vendors.js`, not inline within the `amp-analytics` tag, nor via remote configuration.
Expand All @@ -673,6 +677,23 @@ In the example below, an `iframe` URL is not specified, and `beacon` and `xhrpos

To learn more, see [this example that implements iframe transport client API](https://github.com/ampproject/amphtml/blob/master/examples/analytics-iframe-transport-remote-frame.html) and [this example page that incorporates that iframe](https://github.com/ampproject/amphtml/blob/master/examples/analytics-iframe-transport.amp.html). The example loads a [fake ad](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html), which contains the `amp-analytics` tag. Note that the fake ad content includes some extra configuration instructions that must be followed.

##### Use Body for Extra URL Params

The `useBody` configuration option indicates whether or not to include `extraUrlParams` in the POST request body instead of in the URL as URL-encoded query parameters.

`useBody` is only available for the `beacon` and `xhrpost` transport methods. If `useBody` is true and used in conjunction with either of these transport methods, `extraUrlParams` are sent in the POST request body. Otherwise, the request is sent with an empty body and the `extraUrlParams` are included as URL parameters.

With `useBody`, you can include nested objects in `extraUrlParams`. However, if the request falls back to other transport options that don't support `useBody` (e.g. `image`), then those nested objects will be stringified into the URL as `[object Object]`.

```javascript
"transport": {
"beacon": true,
"xhrpost": true,
"useBody": true,
"image": false
}
```

##### Referrer Policy

Referrer policy can be specified as `referrerPolicy` field in the `transport` config. Currently only `no-referrer` is supported.
Expand Down
65 changes: 63 additions & 2 deletions test/integration/test-amp-analytics.js
Expand Up @@ -197,7 +197,14 @@ describe.configure().skipIfPropertiesObfuscated().run('amp' +
},
"extraUrlParams": {
"a": 1,
"b": "\${title}"
"b": "\${title}",
"c": {
"d": "\${title}",
"e": {
"f": ["\${title}", "\${title}"]
}
},
"g": ["\${title}", "\${title}"]
}
}
</script>
Expand All @@ -207,7 +214,10 @@ describe.configure().skipIfPropertiesObfuscated().run('amp' +
it('should send request use POST body payload', () => {
return RequestBank.withdraw().then(req => {
expect(req.url).to.equal('/');
expect(JSON.parse(req.body)).to.deep.equal({a: 2, b: 'AMP TEST'});
expect(req.body).to.equal(
'{"a":2,"b":"AMP TEST","c":{"d":"AMP TEST",' +
'"e":{"f":["AMP TEST","AMP TEST"]}},"g":["AMP TEST","AMP TEST"]}'
);
});
});
});
Expand Down Expand Up @@ -263,6 +273,57 @@ describe.configure().skipIfPropertiesObfuscated().run('amp' +
});
});

describes.integration('amp-analytics useBody with extraUrlParamsReplaceMap', {
body:
`<amp-analytics>
<script type="application/json">
{
"requests": {
"endpoint": "${RequestBank.getUrl()}"
},
"triggers": {
"pageview": {
"on": "visible",
"request": "endpoint",
"extraUrlParams": {
"context.a": 2
}
}
},
"transport": {
"beacon": false,
"xhrpost": true,
"useBody": true
},
"extraUrlParamsReplaceMap": {
"context.": "_c_"
},
"extraUrlParams": {
"context.a": 1,
"context.b": {
"context.c": "\${title}",
"context.d": {
"context.e": ["\${title}", "\${title}"]
}
}
}
}
</script>
</amp-analytics>`,
extensions: ['amp-analytics'],
}, () => {
it('should only replace params for top-level keys', () => {
return RequestBank.withdraw().then(req => {
expect(req.url).to.equal('/');
expect(req.body).to.equal(
'{"_c_a":2,"_c_b":{"context.c":"AMP TEST",' +
'"context.d":{"context.e":["AMP TEST","AMP TEST"]}}}'
);
});
});
});


describes.integration('amp-analytics referrerPolicy', {
body:
`<amp-analytics>
Expand Down

0 comments on commit a295a03

Please sign in to comment.