From 7467024ace2ac0de26127364bcf2c2afa1b126f1 Mon Sep 17 00:00:00 2001 From: Anton Baranov Date: Mon, 27 Sep 2021 13:17:00 -0700 Subject: [PATCH 1/4] Yieldmo Synthetic Inventory Module: initial commit --- modules/yieldmoSyntheticInventoryModule.js | 42 ++++++++++++ modules/yieldmoSyntheticInventoryModule.md | 68 +++++++++++++++++++ .../yieldmoSyntheticInventoryModule_spec.js | 37 ++++++++++ 3 files changed, 147 insertions(+) create mode 100644 modules/yieldmoSyntheticInventoryModule.js create mode 100644 modules/yieldmoSyntheticInventoryModule.md create mode 100644 test/spec/modules/yieldmoSyntheticInventoryModule_spec.js diff --git a/modules/yieldmoSyntheticInventoryModule.js b/modules/yieldmoSyntheticInventoryModule.js new file mode 100644 index 00000000000..6ef397ea921 --- /dev/null +++ b/modules/yieldmoSyntheticInventoryModule.js @@ -0,0 +1,42 @@ +import { config } from '../src/config.js'; + +export const MODULE_NAME = 'Yieldmo Synthetic Inventory Module'; + +export function init(config) { + validateConfig(config); + + window.top.googletag = window.top.googletag || {cmd: []}; + + const googletag = window.top.googletag; + const containerName = 'ym_sim_container_' + config.placementId; + + googletag.cmd.push(() => { + if (window.top.document.body) { + googletagCmd(config, containerName, googletag); + } else { + document.addEventListener('DOMContentLoaded', () => googletagCmd(config, containerName, googletag)); + } + }); +}; + +export function validateConfig(config) { + if (!('placementId' in config)) { + throw new Error(`${MODULE_NAME}: placementId required`); + } + if (!('adUnitPath' in config)) { + throw new Error(`${MODULE_NAME}: adUnitPath required`); + } +} + +function googletagCmd(config, containerName, googletag) { + const gamContainer = window.top.document.createElement('div'); + gamContainer.id = containerName; + window.top.document.body.appendChild(gamContainer); + googletag.defineSlot(config.adUnitPath, [1, 1], containerName) + .addService(googletag.pubads()) + .setTargeting('ym_sim_p_id', config.placementId); + googletag.enableServices(); + googletag.display(containerName); +} + +config.getConfig('yieldmo_synthetic_inventory', config => init(config.yieldmo_synthetic_inventory)); diff --git a/modules/yieldmoSyntheticInventoryModule.md b/modules/yieldmoSyntheticInventoryModule.md new file mode 100644 index 00000000000..534db21735c --- /dev/null +++ b/modules/yieldmoSyntheticInventoryModule.md @@ -0,0 +1,68 @@ +# Yieldmo Synthetic Inventory Module + +## Overview + +This module enables publishers to set up Yieldmo Synthetic Outstream ads on their pages. + +If publishers will enable this module and provide placementId and Google Ad Manager ad unit path, this module will create a placement on the page and inject Yieldmo SDK into this placement. Publisher will then need to get a placement id from their Yieldmo account manager (accounts email) and setup corresponding ad units on the GAM ad server. + +## Integration + +Build the Yieldmo Synthetic Inventory Module into the Prebid.js package with: + +``` +gulp build --modules=yieldmoSyntheticInventoryModule,... +``` + +## Module Configuration + +```js +pbjs.que.push(function() { + pbjs.setConfig({ + ym_synthetic_inventory: { + placementId: '1234567890', + adUnitPath: '/1234567/ad_unit_name_used_in_gam' + } + }); +}); +``` + +### Configuration Parameters + +|Name |Scope |Description | Example| Type +| :------------ | :------------ | :------------ | :------------ | :------------ | +|placementId | required | Yieldmo placement ID | '1234567890' | string +|adUnitPath | required | Google Ad Manager ad unit path | '/6355419/ad_unit_name_used_in_gam' | string + +### How to get ad unit path + +Ad unit path follows the format /network-code/[parent-ad-unit-code/.../]ad-unit-code, where: + +- network-code is a unique identifier for the Ad Manager network the ad unit belongs to +- parent-ad-unit-code are the codes of all parent ad units (only applies to non-top level ad units) +- ad-unit-code is the code for the ad unit to be displayed + +Note that all ad unit codes included in the ad unit path must adhere to the [formatting rules](https://support.google.com/admanager/answer/1628457#ad-unit-codes) specified by Ad Manager. + +Another and probably the easiest way to get an ad unit path is to get it from the google ad manager ad unit document header generated tag: + +```js +googletag.defineSlot('/1234567/ad_unit_name_used_in_gam', [1, 1], 'ad-container-id').addService(googletag.pubads()); +``` + +### How to get Yieldmo placement id + +Please reach out to your Yieldmo account's person or email to support@yieldmo.com + +### Google Ad Manager setup + +Yieldmo Synthetic Inventory Module is designed to be used along with Google Ad Manager. GAM should be set as usual, but there are a few requirements: + +- Ad unit size should be 1x1 +- Creative should NOT be served into a SafeFrame and also should have 1x1 size +- Synthetic Inventory Universal Tag should be used as 3rd party creative code +### Synthetic Inventory Universal Tag + +```js +
+``` \ No newline at end of file diff --git a/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js b/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js new file mode 100644 index 00000000000..8024820f398 --- /dev/null +++ b/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js @@ -0,0 +1,37 @@ +import { expect } from 'chai'; +import { + init, + MODULE_NAME, + validateConfig +} from 'modules/yieldmoSyntheticInventoryModule'; + +const mockedYmConfig = { + placementId: '123456', + adUnitPath: '/6355419/ad_unit_name_used_in_gam' +}; + +describe('Yieldmo Synthetic Inventory Module', function() { + let config = Object.assign({}, mockedYmConfig); + + it('should be enabled with valid required params', function() { + expect(function () { + init(mockedYmConfig); + }).not.to.throw() + }); + + it('should throw an error if placementId is missed', function() { + const {placementId, ...config} = mockedYmConfig; + + expect(function () { + validateConfig(config); + }).throw(`${MODULE_NAME}: placementId required`) + }); + + it('should throw an error if adUnitPath is missed', function() { + const {adUnitPath, ...config} = mockedYmConfig; + + expect(function () { + validateConfig(config); + }).throw(`${MODULE_NAME}: adUnitPath required`) + }); +}); From 8d73b0f46f88f48ae4030aad7b47516312244324 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 15 Oct 2021 08:46:25 -0700 Subject: [PATCH 2/4] Updating Documentation --- modules/yieldmoSyntheticInventoryModule.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/yieldmoSyntheticInventoryModule.md b/modules/yieldmoSyntheticInventoryModule.md index 534db21735c..dd6f0acf884 100644 --- a/modules/yieldmoSyntheticInventoryModule.md +++ b/modules/yieldmoSyntheticInventoryModule.md @@ -19,7 +19,7 @@ gulp build --modules=yieldmoSyntheticInventoryModule,... ```js pbjs.que.push(function() { pbjs.setConfig({ - ym_synthetic_inventory: { + yieldmo_synthetic_inventory: { placementId: '1234567890', adUnitPath: '/1234567/ad_unit_name_used_in_gam' } From fd1cec9c8d21330d9df710c5d87aa977b30d0237 Mon Sep 17 00:00:00 2001 From: Dmitriy Labuzov Date: Mon, 18 Oct 2021 18:35:00 +0300 Subject: [PATCH 3/4] Test coverage improved --- modules/yieldmoSyntheticInventoryModule.js | 2 +- .../yieldmoSyntheticInventoryModule_spec.js | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/modules/yieldmoSyntheticInventoryModule.js b/modules/yieldmoSyntheticInventoryModule.js index 6ef397ea921..542f4fb8982 100644 --- a/modules/yieldmoSyntheticInventoryModule.js +++ b/modules/yieldmoSyntheticInventoryModule.js @@ -17,7 +17,7 @@ export function init(config) { document.addEventListener('DOMContentLoaded', () => googletagCmd(config, containerName, googletag)); } }); -}; +} export function validateConfig(config) { if (!('placementId' in config)) { diff --git a/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js b/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js index 8024820f398..4de08ffd0d9 100644 --- a/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js +++ b/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js @@ -10,9 +10,32 @@ const mockedYmConfig = { adUnitPath: '/6355419/ad_unit_name_used_in_gam' }; +const setGoogletag = () => { + window.top.googletag = { + cmd: [], + defineSlot: sinon.stub(), + addService: sinon.stub(), + pubads: sinon.stub(), + setTargeting: sinon.stub(), + enableServices: sinon.stub(), + display: sinon.stub(), + }; + window.top.googletag.defineSlot.returns(window.top.googletag); + window.top.googletag.addService.returns(window.top.googletag); + return window.top.googletag; +} + describe('Yieldmo Synthetic Inventory Module', function() { let config = Object.assign({}, mockedYmConfig); + beforeEach(function () { + delete window.top.googletag; + }); + + afterEach(function () { + delete window.top.googletag; + }); + it('should be enabled with valid required params', function() { expect(function () { init(mockedYmConfig); @@ -34,4 +57,30 @@ describe('Yieldmo Synthetic Inventory Module', function() { validateConfig(config); }).throw(`${MODULE_NAME}: adUnitPath required`) }); + + it('should add correct googletag.cmd', function() { + const containerName = 'ym_sim_container_' + mockedYmConfig.placementId; + const gtag = setGoogletag(); + + init(mockedYmConfig); + + expect(gtag.cmd.length).to.equal(1); + + gtag.cmd[0](); + + expect(gtag.addService.getCall(0)).to.not.be.null; + expect(gtag.setTargeting.getCall(0)).to.not.be.null; + expect(gtag.setTargeting.getCall(0).args[0]).to.exist.and.to.equal('ym_sim_p_id'); + expect(gtag.setTargeting.getCall(0).args[1]).to.exist.and.to.equal(mockedYmConfig.placementId); + expect(gtag.defineSlot.getCall(0)).to.not.be.null; + expect(gtag.enableServices.getCall(0)).to.not.be.null; + expect(gtag.display.getCall(0)).to.not.be.null; + expect(gtag.display.getCall(0).args[0]).to.exist.and.to.equal(containerName); + expect(gtag.pubads.getCall(0)).to.not.be.null; + + const gamContainerEl = window.top.document.getElementById(containerName); + expect(gamContainerEl).to.not.be.null; + + gamContainerEl.parentNode.removeChild(gamContainerEl); + }); }); From 7a9c720b1c706749eba91b9cc595dd71e2da6a5d Mon Sep 17 00:00:00 2001 From: Dmitriy Labuzov Date: Tue, 19 Oct 2021 18:16:48 +0300 Subject: [PATCH 4/4] Fixes after PR (using isGptPubadsDefined and window.googletag instead of window.top.googletag) --- modules/yieldmoSyntheticInventoryModule.js | 16 ++++++++++------ .../yieldmoSyntheticInventoryModule_spec.js | 17 ++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/modules/yieldmoSyntheticInventoryModule.js b/modules/yieldmoSyntheticInventoryModule.js index 542f4fb8982..bca778a7b43 100644 --- a/modules/yieldmoSyntheticInventoryModule.js +++ b/modules/yieldmoSyntheticInventoryModule.js @@ -1,20 +1,24 @@ import { config } from '../src/config.js'; +import { isGptPubadsDefined } from '../src/utils.js'; export const MODULE_NAME = 'Yieldmo Synthetic Inventory Module'; export function init(config) { validateConfig(config); - window.top.googletag = window.top.googletag || {cmd: []}; + if (!isGptPubadsDefined()) { + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + } - const googletag = window.top.googletag; + const googletag = window.googletag; const containerName = 'ym_sim_container_' + config.placementId; googletag.cmd.push(() => { - if (window.top.document.body) { + if (window.document.body) { googletagCmd(config, containerName, googletag); } else { - document.addEventListener('DOMContentLoaded', () => googletagCmd(config, containerName, googletag)); + window.document.addEventListener('DOMContentLoaded', () => googletagCmd(config, containerName, googletag)); } }); } @@ -29,9 +33,9 @@ export function validateConfig(config) { } function googletagCmd(config, containerName, googletag) { - const gamContainer = window.top.document.createElement('div'); + const gamContainer = window.document.createElement('div'); gamContainer.id = containerName; - window.top.document.body.appendChild(gamContainer); + window.document.body.appendChild(gamContainer); googletag.defineSlot(config.adUnitPath, [1, 1], containerName) .addService(googletag.pubads()) .setTargeting('ym_sim_p_id', config.placementId); diff --git a/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js b/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js index 4de08ffd0d9..55b4e7255f7 100644 --- a/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js +++ b/test/spec/modules/yieldmoSyntheticInventoryModule_spec.js @@ -11,7 +11,7 @@ const mockedYmConfig = { }; const setGoogletag = () => { - window.top.googletag = { + window.googletag = { cmd: [], defineSlot: sinon.stub(), addService: sinon.stub(), @@ -20,20 +20,23 @@ const setGoogletag = () => { enableServices: sinon.stub(), display: sinon.stub(), }; - window.top.googletag.defineSlot.returns(window.top.googletag); - window.top.googletag.addService.returns(window.top.googletag); - return window.top.googletag; + window.googletag.defineSlot.returns(window.googletag); + window.googletag.addService.returns(window.googletag); + window.googletag.pubads.returns({getSlots: sinon.stub()}); + return window.googletag; } describe('Yieldmo Synthetic Inventory Module', function() { let config = Object.assign({}, mockedYmConfig); + let googletagBkp; beforeEach(function () { - delete window.top.googletag; + googletagBkp = window.googletag; + delete window.googletag; }); afterEach(function () { - delete window.top.googletag; + window.googletag = googletagBkp; }); it('should be enabled with valid required params', function() { @@ -78,7 +81,7 @@ describe('Yieldmo Synthetic Inventory Module', function() { expect(gtag.display.getCall(0).args[0]).to.exist.and.to.equal(containerName); expect(gtag.pubads.getCall(0)).to.not.be.null; - const gamContainerEl = window.top.document.getElementById(containerName); + const gamContainerEl = window.document.getElementById(containerName); expect(gamContainerEl).to.not.be.null; gamContainerEl.parentNode.removeChild(gamContainerEl);