Skip to content

Commit

Permalink
Add SSR experiments to a4a eid (#30524)
Browse files Browse the repository at this point in the history
* Add SSR experiments to a4a eid

This adds the currently set SSR experiments to the a4a eid.

A small hiccup with this setup is that the SSR experiments are usually just boolean values (or small enums) that don't have a unique experiment id. I've created a pseudo id by concatenating the proto field id (eg, `25`) with a padded value (eg, `0`) to create the exp id (`2500`).

Re: #30504
Re: cl/335090010

* Fix indexing bug

* Remove accidental file commit

* Change to use aexp URL param

See cl/335758969

* Fix tests

* Add a4a utils tests

* Add traffic-experiments tests

* Rename local variable

* Update comment
  • Loading branch information
jridgewell committed Oct 15, 2020
1 parent f8da5fc commit c15a28f
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 66 deletions.
42 changes: 41 additions & 1 deletion ads/google/a4a/test/test-traffic-experiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* limitations under the License.
*/

import {EXPERIMENT_ATTRIBUTE} from '../utils';
import {AMP_EXPERIMENT_ATTRIBUTE, EXPERIMENT_ATTRIBUTE} from '../utils';
import {
addAmpExperimentIdToElement,
addExperimentIdToElement,
isInExperiment,
validateExperimentIds,
Expand Down Expand Up @@ -84,6 +85,45 @@ describe('all-traffic-experiments-tests', () => {
});
});

describe('#addAmpExperimentIdToElement', () => {
it('should add attribute when there is none present to begin with', () => {
const element = document.createElement('div');
expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.not.be.ok;
addAmpExperimentIdToElement('3', element);
expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal('3');
});

it('should append experiment to already valid single experiment', () => {
const element = document.createElement('div');
element.setAttribute(AMP_EXPERIMENT_ATTRIBUTE, '99');
addAmpExperimentIdToElement('3', element);
expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal('99,3');
});

it('should do nothing to already valid single experiment', () => {
const element = document.createElement('div');
element.setAttribute(AMP_EXPERIMENT_ATTRIBUTE, '99');
addAmpExperimentIdToElement(undefined, element);
expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal('99');
});

it('should append experiment to already valid multiple experiments', () => {
const element = document.createElement('div');
element.setAttribute(AMP_EXPERIMENT_ATTRIBUTE, '99,77,11,0122345');
addAmpExperimentIdToElement('3', element);
expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal(
'99,77,11,0122345,3'
);
});

it('should should replace existing invalid experiments', () => {
const element = document.createElement('div');
element.setAttribute(AMP_EXPERIMENT_ATTRIBUTE, '99,14,873,k,44');
addAmpExperimentIdToElement('3', element);
expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal('3');
});
});

describe('#isInExperiment', () => {
it('should return false for empty element and any query', () => {
const element = document.createElement('div');
Expand Down
12 changes: 9 additions & 3 deletions ads/google/a4a/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import '../../../../extensions/amp-ad/0.1/amp-ad-ui';
import '../../../../extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler';
import * as IniLoad from '../../../../src/ini-load';
import {CONSENT_POLICY_STATE} from '../../../../src/consent-state';
import {
AMP_EXPERIMENT_ATTRIBUTE,
EXPERIMENT_ATTRIBUTE,
TRUNCATION_PARAM,
ValidAdContainerTypes,
Expand All @@ -37,6 +37,7 @@ import {
maybeAppendErrorParameter,
mergeExperimentIds,
} from '../utils';
import {CONSENT_POLICY_STATE} from '../../../../src/consent-state';
import {MockA4AImpl} from '../../../../extensions/amp-a4a/0.1/test/utils';
import {Services} from '../../../../src/services';
import {buildUrl} from '../shared/url-builder';
Expand Down Expand Up @@ -203,7 +204,7 @@ describe('Google A4A utils', () => {
'width': '200',
'height': '50',
'type': 'adsense',
'data-experiment-id': '00000001,0000002',
[EXPERIMENT_ATTRIBUTE]: '00000001,0000002',
});
const a4a = new MockA4AImpl(element);
url = 'not an array';
Expand Down Expand Up @@ -251,6 +252,8 @@ describe('Google A4A utils', () => {
switch (name) {
case EXPERIMENT_ATTRIBUTE:
return '00000001,00000002';
case AMP_EXPERIMENT_ATTRIBUTE:
return '103,204';
case 'type':
return 'fake-type';
case 'data-amp-slot-index':
Expand Down Expand Up @@ -285,6 +288,7 @@ describe('Google A4A utils', () => {
new RegExp(`(\\?|&)met\\.a4a\\.0=${metricName}\\.-?[0-9]+(&|$)`),
/(\?|&)dt=-?[0-9]+(&|$)/,
/(\?|&)e\.0=00000001%2C00000002(&|$)/,
/(\?|&)aexp=103!204(&|$)/,
/(\?|&)rls=\$internalRuntimeVersion\$(&|$)/,
/(\?|&)adt.0=fake-type(&|$)/,
];
Expand Down Expand Up @@ -428,13 +432,15 @@ describe('Google A4A utils', () => {
'type': 'adsense',
'width': '320',
'height': '50',
'data-experiment-id': '123,456',
[EXPERIMENT_ATTRIBUTE]: '123,456',
[AMP_EXPERIMENT_ATTRIBUTE]: '111,222',
});
const impl = new MockA4AImpl(elem);
noopMethods(impl, fixture.ampdoc, window.sandbox);
return fixture.addElement(elem).then(() => {
return googleAdUrl(impl, '', 0, {}, ['789', '098']).then((url1) => {
expect(url1).to.match(/eid=123%2C456%2C789%2C098/);
expect(url1).to.match(/aexp=111!222/);
});
});
});
Expand Down
44 changes: 36 additions & 8 deletions ads/google/a4a/traffic-experiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
* impacts on click-throughs.
*/

import {EXPERIMENT_ATTRIBUTE, mergeExperimentIds} from './utils';
import {
AMP_EXPERIMENT_ATTRIBUTE,
EXPERIMENT_ATTRIBUTE,
mergeExperimentIds,
} from './utils';
import {
ExperimentInfo, // eslint-disable-line no-unused-vars
} from '../../../src/experiments';
Expand Down Expand Up @@ -141,18 +145,42 @@ export function validateExperimentIds(idList) {
*
* @param {string|undefined} experimentId ID to add to the element.
* @param {Element} element to add the experiment ID to.
* @param {string} attr the attribute name that holds the experiments
*/
export function addExperimentIdToElement(experimentId, element) {
function addExpIdToElement(experimentId, element, attr) {
if (!experimentId) {
return;
}
const currentEids = element.getAttribute(EXPERIMENT_ATTRIBUTE);
const currentEids = element.getAttribute(attr);
if (currentEids && validateExperimentIds(parseExperimentIds(currentEids))) {
element.setAttribute(
EXPERIMENT_ATTRIBUTE,
mergeExperimentIds([experimentId], currentEids)
);
element.setAttribute(attr, mergeExperimentIds([experimentId], currentEids));
} else {
element.setAttribute(EXPERIMENT_ATTRIBUTE, experimentId);
element.setAttribute(attr, experimentId);
}
}

/**
* Adds a single experimentID to an element iff it's a valid experiment ID.
* No-ops if the experimentId is undefined.
*
* @param {string|undefined} experimentId ID to add to the element.
* @param {Element} element to add the experiment ID to.
*/
export function addExperimentIdToElement(experimentId, element) {
addExpIdToElement(experimentId, element, EXPERIMENT_ATTRIBUTE);
}

/**
* Adds a single AMP experimentID to an element iff it's a valid experiment ID.
* No-ops if the experimentId is undefined.
*
* Note that AMP experiment brances do not have their own unique IDs. Instead,
* we generate a pseudo ID for them by concatenating the id with the
* experiment's value.
*
* @param {string|undefined} experimentId ID to add to the element.
* @param {Element} element to add the experiment ID to.
*/
export function addAmpExperimentIdToElement(experimentId, element) {
addExpIdToElement(experimentId, element, AMP_EXPERIMENT_ATTRIBUTE);
}
25 changes: 23 additions & 2 deletions ads/google/a4a/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const QQID_HEADER = 'X-QQID';
export const SANDBOX_HEADER = 'amp-ff-sandbox';

/**
* Element attribute that stores experiment IDs.
* Element attribute that stores Google ads experiment IDs.
*
* Note: This attribute should be used only for tracking experimental
* implementations of AMP tags, e.g., by AMPHTML implementors. It should not be
Expand All @@ -84,6 +84,18 @@ export const SANDBOX_HEADER = 'amp-ff-sandbox';
*/
export const EXPERIMENT_ATTRIBUTE = 'data-experiment-id';

/**
* Element attribute that stores AMP experiment IDs.
*
* Note: This attribute should be used only for tracking experimental
* implementations of AMP tags, e.g., by AMPHTML implementors. It should not be
* added by a publisher page.
*
* @const {string}
* @visibleForTesting
*/
export const AMP_EXPERIMENT_ATTRIBUTE = 'data-amp-experiment-id';

/** @typedef {{urls: !Array<string>}}
*/
export let AmpAnalyticsConfigDef;
Expand Down Expand Up @@ -187,10 +199,11 @@ export function googleBlockParameters(a4a, opt_experimentIds) {
const slotRect = a4a.getPageLayoutBox();
const iframeDepth = iframeNestingDepth(win);
const enclosingContainers = getEnclosingContainerTypes(adElement);
let eids = adElement.getAttribute('data-experiment-id');
let eids = adElement.getAttribute(EXPERIMENT_ATTRIBUTE);
if (opt_experimentIds) {
eids = mergeExperimentIds(opt_experimentIds, eids);
}
const aexp = adElement.getAttribute(AMP_EXPERIMENT_ATTRIBUTE);
return {
'adf': DomFingerprint.generate(adElement),
'nhd': iframeDepth,
Expand All @@ -199,6 +212,8 @@ export function googleBlockParameters(a4a, opt_experimentIds) {
'ady': Math.round(slotRect.top),
'oid': '2',
'act': enclosingContainers.length ? enclosingContainers.join() : null,
// aexp URL param is separated by `!`, not `,`.
'aexp': aexp ? aexp.replace(/,/g, '!') : null,
};
}

Expand Down Expand Up @@ -779,6 +794,11 @@ export function addCsiSignalsToAmpAnalyticsConfig(
const correlator = getCorrelator(win, element);
const slotId = Number(element.getAttribute('data-amp-slot-index'));
const eids = encodeURIComponent(element.getAttribute(EXPERIMENT_ATTRIBUTE));
let aexp = element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE);
if (aexp) {
// aexp URL param is separated by `!`, not `,`.
aexp = aexp.replace(/,/g, '!');
}
const adType = element.getAttribute('type');
const initTime = Number(
getTimingDataSync(win, 'navigationStart') || Date.now()
Expand All @@ -793,6 +813,7 @@ export function addCsiSignalsToAmpAnalyticsConfig(
`&c=${correlator}&slotId=${slotId}&qqid.${slotId}=${qqid}` +
`&dt=${initTime}` +
(eids != 'null' ? `&e.${slotId}=${eids}` : '') +
(aexp ? `&aexp=${aexp}` : '') +
`&rls=${internalRuntimeVersion()}&adt.${slotId}=${adType}`;
const isAmpSuffix = isVerifiedAmpCreative ? 'Friendly' : 'CrossDomain';
config['triggers']['continuousVisibleIniLoad'] = {
Expand Down
35 changes: 35 additions & 0 deletions extensions/amp-a4a/0.1/amp-a4a.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {installUrlReplacementsForEmbed} from '../../../src/service/url-replaceme
import {isAdPositionAllowed} from '../../../src/ad-helper';
import {isArray, isEnumValue, isObject} from '../../../src/types';
import {listenOnce} from '../../../src/event-helper';
import {padStart} from '../../../src/string';
import {parseJson} from '../../../src/json';
import {processHead} from './head-validation';
import {setStyle} from '../../../src/style';
Expand Down Expand Up @@ -2308,6 +2309,40 @@ export class AmpA4A extends AMP.BaseElement {
}
return null;
}

/**
* Returns any enabled SSR experiments via the amp-usqp meta tag. These
* correspond to the proto field ids in cs/AmpTransformerParams.
*
* These experiments do not have a fully unique experiment id for each value,
* so we concatenate the key and value to generate a psuedo id. We assume
* that any experiment is either a boolean (so two branches), or an enum with
* 100 or less branches. So, the value is padded a leading 0 if necessary.
*
* @protected
* @return {!Array<string>}
*/
getSsrExpIds_() {
const exps = [];
const meta = this.getAmpDoc().getMetaByName('amp-usqp');
if (meta) {
const keyValues = meta.split(',');
for (let i = 0; i < keyValues.length; i++) {
const kv = keyValues[i].split('=');
if (kv.length !== 2) {
continue;
}
// Reasonably assume that all important exps are either booleans, or
// enums with 100 or less branches.
const val = Number(kv[1]);
if (!isNaN(kv[0]) && val >= 0 && val < 100) {
const padded = padStart(kv[1], 2, '0');
exps.push(kv[0] + padded);
}
}
}
return exps;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
import {ResponsiveState} from './responsive-state';
import {Services} from '../../../src/services';
import {
addAmpExperimentIdToElement,
addExperimentIdToElement,
isInManualExperiment,
} from '../../../ads/google/a4a/traffic-experiments';
Expand Down Expand Up @@ -231,6 +232,10 @@ export class AmpAdNetworkAdsenseImpl extends AmpA4A {
if (moduleNomoduleExpId) {
addExperimentIdToElement(moduleNomoduleExpId, this.element);
}
const ssrExpIds = this.getSsrExpIds_();
for (let i = 0; i < ssrExpIds.length; i++) {
addAmpExperimentIdToElement(ssrExpIds[i], this.element);
}
}

/**
Expand Down

0 comments on commit c15a28f

Please sign in to comment.