From 7867de93a89ac2b707ee42f439fa13be3f2e9fbd Mon Sep 17 00:00:00 2001 From: glevitzky Date: Tue, 7 Nov 2017 08:38:02 -0500 Subject: [PATCH] Troubleshoot for AMP pages (#11920) * Init. * stashing changes. * Cases slotid correctly. * Added test and finalized message attributes. * Removing test code. * Clean up + negative test. * Added comments. * Fixed comments and test. * Fixing presubmit errors. * Make more obvious what negative test is testing. * Gathering all Troubleshoot data into single type/object. * Fixing docstring. * Moved troubleshootData initializations earlier into constructor. * Made postTroubleshootMessage public and visible for testing. * Moving things back to buildCallback. --- .../0.1/amp-ad-network-doubleclick-impl.js | 66 ++++++++++++++++- .../test-amp-ad-network-doubleclick-impl.js | 74 +++++++++++++++++++ 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js b/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js index dffd101dff12..cab3fdb3ce0c 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js @@ -141,6 +141,15 @@ export const SAFEFRAME_ORIGIN = 'https://tpc.googlesyndication.com'; /** @private {?Promise} */ let sraRequests = null; +/** @typedef {{ + adUrl: !Promise, + lineItemId: string, + creativeId: string, + slotId: string, + slotIndex: string, + }} */ +let TroubleshootData; + /** * Array of functions used to combine block level request parameters for SRA * request. @@ -353,6 +362,9 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { /** @private {boolean} */ this.preloadSafeframe_ = true; + + /** @private {!TroubleshootData} */ + this.troubleshootData_ = /** @type {!TroubleshootData} */ ({}); } /** @override */ @@ -394,6 +406,9 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { .then(() => getIdentityToken(this.win, this.getAmpDoc())) : Promise.resolve( /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({})); + this.troubleshootData_.slotId = this.element.getAttribute('data-slot'); + this.troubleshootData_.slotIndex = + this.element.getAttribute('data-amp-slot-index'); if (this.win['dbclk_a4a_viz_change']) { // Only create one per page but ensure all slots get experiment // selection. @@ -598,8 +613,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // On error/timeout, proceed. return /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({}); }); - return Promise.all([opt_rtcResponsesPromise, identityPromise]).then( - results => { + const urlPromise = Promise.all([opt_rtcResponsesPromise, identityPromise]) + .then(results => { const rtcParams = this.mergeRtcResponses_(results[0]); this.identityToken = results[1]; return googleAdUrl( @@ -607,6 +622,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { this.getBlockParameters_(), rtcParams, this.buildIdentityParams_(), PAGE_LEVEL_PARAMS_)); }); + this.troubleshootData_.adUrl = urlPromise; + return urlPromise; } /** @@ -695,6 +712,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { setGoogleLifecycleVarsFromHeaders(responseHeaders, this.lifecycleReporter_); this.ampAnalyticsConfig_ = extractAmpAnalyticsConfig(this, responseHeaders); this.qqid_ = responseHeaders.get(QQID_HEADER); + this.troubleshootData_.creativeId = + responseHeaders.get('google-creative-id'); + this.troubleshootData_.lineItemId = + responseHeaders.get('google-lineitem-id'); if (this.ampAnalyticsConfig_) { // Load amp-analytics extensions this.extensions_./*OK*/installExtensionForDoc( @@ -908,6 +929,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } return true; }); + + this.postTroubleshootMessage(); } /** @@ -1209,6 +1232,45 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { this.win.removeEventListener('message', fluidMessageListener_); } } + + /** + * Emits a postMessage containing information about this slot to the DFP + * Troubleshoot UI. A promise is returned if a message is posted, otherwise + * null is returned. The promise is returned only for test convenience. + * + * @return {?Promise} + * @visibleForTesting + */ + postTroubleshootMessage() { + if (!this.win.opener || !/[?|&]dfpdeb/.test(this.win.location.search)) { + return null; + } + dev().assert(this.troubleshootData_.adUrl, 'ad URL does not exist yet'); + return this.troubleshootData_.adUrl.then(adUrl => { + const payload = dict({ + 'gutData': JSON.stringify(dict({ + 'events': [{ + 'timestamp': Date.now(), + 'slotid': this.troubleshootData_.slotId, + 'messageId': 4, + }], + 'slots': [{ + 'contentUrl': adUrl || '', + 'id': this.troubleshootData_.slotId, + 'leafAdUnitName': this.troubleshootData_.slotId, + 'domId': 'gpt_unit_' + this.troubleshootData_.slotId + '_' + + this.troubleshootData_.slotIndex, + 'lineItemId': this.troubleshootData_.lineItemId, + 'creativeId': this.troubleshootData_.creativeId, + }], + })), + 'userAgent': navigator.userAgent, + 'referrer': this.win.location.href, + 'messageType': 'LOAD', + }); + this.win.opener./*OK*/postMessage(payload, '*'); + }); + } } diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js index d2f0c3a19043..2a1e801ea3b2 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js @@ -1064,6 +1064,80 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { ['https://partner.googleadservices.com', SAFEFRAME_ORIGIN]); }); }); + + describe('Troubleshoot for AMP pages', () => { + beforeEach(() => { + element = doc.createElement('amp-ad'); + element.setAttribute('type', 'doubleclick'); + doc.body.appendChild(element); + impl = new AmpAdNetworkDoubleclickImpl(element); + impl.troubleshootData_ = { + adUrl: Promise.resolve('http://www.getmesomeads.com'), + creativeId: '123', + lineItemId: '456', + slotId: 'slotId', + slotIndex: '0', + }; + }); + + afterEach(() => { + doc.body.removeChild(element); + }); + + it('should emit post message', () => { + const slotId = 'slotId'; + env.win = { + location: { + href: 'http://localhost:8000/foo?dfpdeb', + search: '?dfpdeb', + }, + opener: { + postMessage: payload => { + expect(payload).to.be.ok; + expect(payload.userAgent).to.be.ok; + expect(payload.referrer).to.be.ok; + expect(payload.messageType).to.equal('LOAD'); + + const gutData = JSON.parse(payload.gutData); + expect(gutData).to.be.ok; + expect(gutData.events[0].timestamp).to.be.ok; + expect(gutData.events[0].slotid).to.equal(slotId); + expect(gutData.events[0].messageId).to.equal(4); + + expect(gutData.slots[0].contentUrl).to + .equal('http://www.getmesomeads.com'); + expect(gutData.slots[0].id).to.equal(slotId); + expect(gutData.slots[0].leafAdUnitName).to.equal(slotId); + expect(gutData.slots[0].domId).to.equal( + 'gpt_unit_' + slotId + '_0'); + expect(gutData.slots[0].creativeId).to.equal('123'); + expect(gutData.slots[0].lineItemId).to.equal('456'); + }, + }, + }; + const postMessageSpy = sandbox.spy(env.win.opener, 'postMessage'); + impl.win = env.win; + return impl.postTroubleshootMessage().then(() => + expect(postMessageSpy).to.be.calledOnce); + }); + + it('should not emit post message', () => { + env.win = { + location: { + href: 'http://localhost:8000/foo', + search: '', + }, + opener: { + postMessage: () => { + // should never get here + expect(false).to.be.true; + }, + }, + }; + impl.win = env.win; + expect(impl.postTroubleshootMessage()).to.be.null; + }); + }); });