Skip to content

Amp-analytics for AdSense/Doubleclick A4A Implementations#7619

Merged
lannka merged 15 commits intoampproject:masterfrom
google:a4a_amp_analytics
Feb 22, 2017
Merged

Amp-analytics for AdSense/Doubleclick A4A Implementations#7619
lannka merged 15 commits intoampproject:masterfrom
google:a4a_amp_analytics

Conversation

@keithwrightbos
Copy link
Copy Markdown
Contributor

Allow for AdSense/Doubleclick A4A implementations to create an amp-analytics element within amp-ad element that will fire activeview pings whose URLs are based on ad response header.

@dknecht
Copy link
Copy Markdown
Contributor

dknecht commented Feb 16, 2017

Does this basically just auto-inject analytics vs coding it into a4a html?

fyi @oliy

@lannka lannka self-assigned this Feb 16, 2017
});
scriptElem.textContent = JSON.stringify(config);
ampAnalyticsElem.appendChild(scriptElem);
a4a.element.appendChild(ampAnalyticsElem);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure this amp-analytics is well scoped to only collect the data related to the creative. So I guess there you will have to add the tracker programmatically. @dvoytenko do you have a pointer how should this be done?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add scoped attribute to amp-analytics element. E.g. <amp-analytics scoped>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dvoytenko any way to add the scoped tracker directly (instead of adding to DOM and parse again)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet. But we will add.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any downside to having two amp-analytics elements inside the same amp-ad block, in case, say, the publisher has inserted one in the page source? This code adds a second -- will that be problematic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added scoped attribute.

@keithwrightbos
Copy link
Copy Markdown
Contributor Author

Responding to @dknecht question: this creates an amp-analytics element outside of the creative but within the amp-ad element. The implementation is specific to adsense/doubleclick (meaning it is within their impls as opposed to the abstract base class) and could be replicated by any impl should they want/need it.

@keithwrightbos
Copy link
Copy Markdown
Contributor Author

@lannka I'm getting the following error on check-types and am not sure why. It seems to be related by my inclusion of import {createIframePromise} from '../../../../testing/iframe'; in extensions/amp-a4a/0.1/test/utils to allow for centralizing a utility function. All tests appear to pass. Any ideas?

Error compiling ./3p/integration.js
WARNING - Failed to load module "../../../../testing/iframe.js"

@dknecht
Copy link
Copy Markdown
Contributor

dknecht commented Feb 17, 2017

@keithwrightbos Thanks for explanation. Can you explain why that is needed vs done inside a4a creative?

@keithwrightbos
Copy link
Copy Markdown
Contributor Author

@dknecht We want to do this outside the creative for two reasons:

  • We need to support measurement for non-AMP creatives.
  • It easier than ensuring every possible creative returned includes the amp-analytics element

@lannka
Copy link
Copy Markdown
Contributor

lannka commented Feb 17, 2017

@keithwrightbos the check-types problem is weird. I think we don't actually check test files.
Nevertheless, you might want to stop using createIframePromise(). Does describes work for you?

@keithwrightbos
Copy link
Copy Markdown
Contributor Author

@lannka PTAL, I fixed the type check issue. Thanks

Copy link
Copy Markdown

@tdrl tdrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite done with full read-through. Looks reasonable so far, though.

return this.renderAmpCreative_(creativeMetaData)
.then(() => {
protectedOnCreativeRender(true);
return true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that true is the correct / an acceptable return value here? I tried reading renderAmpCreative_, but it has gotten depressingly convoluted. I think that at this point, it returns the result of protectedEmitLifecycleEvent_? Regardless, I don't think (?) there are any downstream consumers of the return value of this promise, so maybe it's okay to just return with no value?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to return at all

return this.renderNonAmpCreative_()
.then(() => {
protectedOnCreativeRender(false);
return true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to return at all

* Callback executed when AMP creative has successfully rendered within the
* Callback executed when creative has successfully rendered within the
* publisher page. To be overridden by network implementations as needed.
* @param {boolean} isVerifiedAmpCreative whether or not the creative was
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Blank line between description and @param.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/**
* Render non-AMP creative within cross domain iframe.
* @return {Promise} awaiting ad completed insertion.
* @return {Promise<boolean>} awaiting ad completed insertion indicating if
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid I can't parse this comment. :-( Rephrase?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* AMP creative.
* @return {!Promise} Whether the creative was successfully
* rendered.
* @return {Promise} Whether the creative was successfully rendered.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{Promise<boolean>}? And why lose the !? It looks like it returns a promise guaranteed to me -- am I missing a pathway?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge issue. fixed

const ADSENSE_BASE_URL = 'https://googleads.g.doubleclick.net/pagead/ads';

/** @type {string} */
const TAG = 'AMP-AD-NETWORK-ADSENSE-IMPL';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be @type {string} or @const {string}? Or does it matter in ES6?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/** @override */
extractCreativeAndSignature(responseText, responseHeaders) {
setGoogleLifecycleVarsFromHeaders(responseHeaders, this.lifecycleReporter_);
if (responseHeaders.has(AMP_ANALYTICS_HEADER)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be factored into shared code between AdSense and Doubleclick impls, similar to setGoogleLifecycleVarsFromHeaders? Maybe in ads/google/a4a/utils.js? Ditto below for onCreativeRender.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

});
});

describe('#injectActiveViewAmpAnalyticsElement', () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're writing this test from scratch, you should probably use the new describes helpers:

describes.sandboxed('#injectActiveViewAmpAnalyticsElement', {}, () => {
  describes.fakeWin('some name', {amp: true}, env => {
    const win = env.win;
    // tests that can access sandbox and win
  }
}

I think that will provide you everything that setupForAdTesting is currently giving you, as well as most of what createIframePromise gives you.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to do this in a later PR as part of moving to describes.sandboxed

Copy link
Copy Markdown

@tdrl tdrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested a couple additional tests, but otherwise LGTM.

/**
* Creates amp-analytics element within a4a element using urls specified
* with amp-ad closest selector and min 50% visible for 1 sec.
* @param {!../../../extensions/amp-a4a/0.1/amp-a4a.AmpA4A} a4a
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Clarify that it's the a4a class and not the element. (Should be obvious from type, but defensiveness is good...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

});
scriptElem.textContent = JSON.stringify(config);
ampAnalyticsElem.appendChild(scriptElem);
a4a.element.appendChild(ampAnalyticsElem);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any downside to having two amp-analytics elements inside the same amp-ad block, in case, say, the publisher has inserted one in the page source? This code adds a second -- will that be problematic?

* with amp-ad closest selector and min 50% visible for 1 sec.
* @param {!../../../extensions/amp-a4a/0.1/amp-a4a.AmpA4A} a4a
* @param {!../../../src/service/extensions-impl.Extensions} extensions
* @param {!Array<string>} urls
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If urls is empty, what's the behavior of this function? In that case, should a block be inserted at all, or should it just skip out?

});
});
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional test cases: 0 urls and 1 url. Also, calling injectActiveViewAmpAnalyticsElement on an amp-ad that already contains an analytics tag (e.g., from publisher page).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 0 urls case (1 is covered by 2 IMO). There is no concern with another amp-analytics element being present.

@keithwrightbos
Copy link
Copy Markdown
Contributor Author

@lannka PTAL and please merge if looks good and build is green. Thanks

Copy link
Copy Markdown
Contributor

@lannka lannka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Please do manual test if the amp-analytics is well scoped.
For example, try to put CLIENT_ID in the URL. we should NOT get the same CLIENT_ID as the host page.

@keithwrightbos
Copy link
Copy Markdown
Contributor Author

I am unable to see a different CLIENT_ID when testing locally. Steps to reproduce. @dvoytenko can you confirm expected behavior? Is the test incorrect?

Add the following item to examples/adsense.amp.html within an amp-ad element with and without scoped attribute. Compare client parameter in generated image urls.

<script type=application/json> { "transport": {"beacon": false, "xhrpost": false}, "requests": { "endpoint": "https://raw.githubusercontent.com/ampproject/amphtml/master/examples/img/ampicon.png", "base": "${endpoint}?scoped=true&client=${clientId}" }, "triggers": { "timer": { "on": "timer", "timerSpec": { "interval": 5, "maxTimerLength": 20 }, "request": "base", "vars": { "clientId": "CLIENT_ID(AMP_ECID_GOOGLE)" } } } } </script>

@lannka
Copy link
Copy Markdown
Contributor

lannka commented Feb 22, 2017

Decided to move on this PR, and fix the scoping issue separately. #7724

@lannka lannka merged commit 3f153b4 into ampproject:master Feb 22, 2017
@keithwrightbos keithwrightbos deleted the a4a_amp_analytics branch February 22, 2017 19:04
mrjoro pushed a commit to mrjoro/amphtml that referenced this pull request Apr 28, 2017
…#7619)

* initial changes

* add test coverage

* remove dependency on createIframePromise causing type check failure

* build failures

* build fix

* fix test failure

* change AV config from urls to url

* PR review

* Add scoped attribute to amp-analytics

* fix lint error

* fix lint error

* fix presubmit error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants