Skip to content

Commit

Permalink
YIEL-3463: Add next GPT features
Browse files Browse the repository at this point in the history
  • Loading branch information
Saigredan committed Mar 23, 2021
1 parent 3507e06 commit 1bfabf3
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 61 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ In addition to above spec use `Action` group to gather all needed actions requir

## Unreleased

### Added
- Extend GPT supported options

## [1.1.0] - 2021-01-12 Production

### Added
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ return (
```

`AdManagerProvider` context component should be placed at the top of your React app. It is responsible for injecting GPT and Yieldbird Wrapper scripts, initializing variables and storing helper data.
| name | type | required | description |
| :---- | :----: | :----: | :---- |
| `collapseEmptyDivs` | boolean | false | Google AdManager collapseEmptyDivs option |
| `globalTargeting` | object | false | targeting object which can be used to pass aditional key-values pairs to pubads object |
| `uuid` | string | true | Yieldbird UUID required to load Wrapper script |
| `onImpressionViewable` | function | false | Callback function for 'impressionViewable' event |
| `onSlotOnload` | function | false | Callback function for 'slotOnload' event |
| `onSlotRender` | function | false | Callback function for 'slotRenderEnded' event |
| `onSlotRequested` | function | false | Callback function for 'slotRequested' event |
| `onSlotResponseReceived` | function | false | Callback function for 'slotResponseReceived' event |
| `onSlotVisibilityChanged` | function | false | Callback function for 'slotVisibilityChanged' event |


`AdManagerSlot` is a simple ad component, with properties similar to GPT slot. It is responsible for rendering ad in specified place. You can use it with following properties:
| name | type | required | description |
Expand Down
12 changes: 11 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@ const App = () => {
}, [toggle, setToggle])

return (
<AdManagerProvider uuid={config.uuid}>
<AdManagerProvider
uuid={config.uuid}
collapseEmptyDivs
globalTargeting={
{
test_global: '1'
}
}
onImpressionViewable={(event) => { console.log('impresion viewable!', event) }}
onSlotRequested={(event) => { console.log('slot requested!', event) }}
>
<div>
<div>
AD 1
Expand Down
16 changes: 10 additions & 6 deletions src/Components/AdManagerSlot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('AdManagerSlot', () => {
})

expect(wrapper.find('#foo')).toBeTruthy()
expect(window.googletag.cmd).toHaveLength(1)
expect(window.googletag.cmd).toHaveLength(2)
expect(window.Yieldbird.cmd).toHaveLength(1)
})

Expand All @@ -94,27 +94,30 @@ describe('AdManagerSlot', () => {
// create slot commands
window.Yieldbird.cmd[0]()
window.googletag.cmd[1]()
window.googletag.cmd[2]()

expect(window.googletag.enableServices).toHaveBeenCalledTimes(1)
expect(window.googletag.defineSlot).toHaveBeenCalledTimes(1)
expect(window.googletag.display).toHaveBeenCalledTimes(1)
expect(window.Yieldbird.setGPTTargeting).toHaveBeenCalledTimes(1)

expect(window.Yieldbird.cmd).toHaveLength(1)
expect(window.googletag.cmd).toHaveLength(2)
expect(window.googletag.cmd).toHaveLength(3)
await wrapper.find('button').simulate('click')

expect(window.googletag.cmd).toHaveLength(3)
expect(window.googletag.cmd).toHaveLength(5)
// destroy slot command
window.googletag.cmd[2]()
window.googletag.cmd[3]()
window.googletag.cmd[4]()

expect(window.googletag.destroySlots).toHaveBeenCalledTimes(1)
await wrapper.find('button').simulate('click')

// create slot command again
expect(window.Yieldbird.cmd).toHaveLength(2)
window.Yieldbird.cmd[1]()
window.googletag.cmd[3]()
window.googletag.cmd[5]()
window.googletag.cmd[6]()

expect(window.googletag.enableServices).toHaveBeenCalledTimes(2)
expect(window.googletag.defineSlot).toHaveBeenCalledTimes(2)
Expand Down Expand Up @@ -209,6 +212,7 @@ describe('AdManagerSlot', () => {
// create slot commands
window.Yieldbird.cmd[0]()
window.googletag.cmd[1]()
window.googletag.cmd[2]()

expect(window.googletag.enableServices).toHaveBeenCalledTimes(1)
expect(window.googletag.defineSlot).toHaveBeenCalledTimes(1)
Expand All @@ -217,7 +221,7 @@ describe('AdManagerSlot', () => {
await jest.advanceTimersByTime(5000)
jest.runAllTimers()

window.googletag.cmd[2]()
window.googletag.cmd[3]()
expect(window.googletag.display).toHaveBeenCalledTimes(1)
expect(global.IntersectionObserver.prototype.unobserve).toHaveBeenCalled()
})
Expand Down
2 changes: 1 addition & 1 deletion src/Context/AdManagerProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ describe('AdManagerSlot', () => {
})

expect(window.Yieldbird?.cmd).toHaveLength(0)
expect(window.googletag?.cmd).toHaveLength(1)
expect(window.googletag?.cmd).toHaveLength(2)
})
})
36 changes: 34 additions & 2 deletions src/Context/AdManagerProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@ import { initializeAdStack } from '../Utils/headerScripts'
import { isIntersectionObserverAvailable } from '../Utils/intersectionObserver'

interface Props {
collapseEmptyDivs?: boolean
globalTargeting?: Record<string, string>
uuid: string
refreshDelay?: number
onImpressionViewable?: (
event: googletag.events.ImpressionViewableEvent
) => void
onSlotOnload?: (event: googletag.events.SlotOnloadEvent) => void
onSlotRender?: (event: googletag.events.SlotRenderEndedEvent) => void
onSlotRequested?: (event: googletag.events.SlotRequestedEvent) => void
onSlotResponseReceived?: (
event: googletag.events.SlotResponseReceived
) => void
onSlotVisibilityChanged?: (
event: googletag.events.SlotVisibilityChangedEvent
) => void
}

export const AdManagerContext = React.createContext({
Expand All @@ -22,11 +36,29 @@ export const AdManagerContext = React.createContext({
export const AdManagerProvider: React.FC<Props> = ({
children,
uuid,
refreshDelay
refreshDelay,
collapseEmptyDivs,
globalTargeting,
onImpressionViewable,
onSlotOnload,
onSlotRender,
onSlotRequested,
onSlotResponseReceived,
onSlotVisibilityChanged
}) => {
const [adsMap, setAdsMap] = useState<string[]>([])

const adManager = new AdManager(refreshDelay)
const adManager = new AdManager(
collapseEmptyDivs,
globalTargeting,
refreshDelay,
onImpressionViewable,
onSlotOnload,
onSlotRender,
onSlotRequested,
onSlotResponseReceived,
onSlotVisibilityChanged
)
const intersectionObserver =
isIntersectionObserverAvailable() &&
new IntersectionObserver((entries, observer) => {
Expand Down
87 changes: 36 additions & 51 deletions src/Utils/AdManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { ensureScripts } from './headerScripts'

import { isIntersectionObserverAvailable } from './intersectionObserver'
import {
createSlot,
initiaizeGlobalGPTOptions,
setTargeting,
setSizeMapping
} from './AdManagerUtils'

export class AdManager {
private adsToRefresh: {
Expand All @@ -19,7 +25,23 @@ export class AdManager {

private retargetTimeout: number

constructor(timeout = 1000) {
constructor(
collapseEmptyDivs?: boolean,
globalTargeting?: Record<string, string>,
timeout = 1000,
onImpressionViewable?: (
event: googletag.events.ImpressionViewableEvent
) => void,
onSlotOnload?: (event: googletag.events.SlotOnloadEvent) => void,
onSlotRender?: (event: googletag.events.SlotRenderEndedEvent) => void,
onSlotRequested?: (event: googletag.events.SlotRequestedEvent) => void,
onSlotResponseReceived?: (
event: googletag.events.SlotResponseReceived
) => void,
onSlotVisibilityChanged?: (
event: googletag.events.SlotVisibilityChangedEvent
) => void
) {
this.adsToRefresh = {}
this.adsToRetarget = {}
this.refreshInterval = null
Expand All @@ -28,6 +50,16 @@ export class AdManager {
this.retargetTimeout = timeout

ensureScripts()
initiaizeGlobalGPTOptions(
collapseEmptyDivs,
globalTargeting,
onImpressionViewable,
onSlotOnload,
onSlotRender,
onSlotRequested,
onSlotResponseReceived,
onSlotVisibilityChanged
)
}

public static defineSlot(
Expand All @@ -43,10 +75,10 @@ export class AdManager {
if (typeof window !== 'undefined') {
window.Yieldbird.cmd.push(() => {
window.googletag.cmd.push(() => {
const slot = this.createSlot(adUnitPath, size, optDiv)
const slot = createSlot(adUnitPath, size, optDiv)

this.setTargeting(slot, targeting)
this.setSizeMapping(slot, sizeMapping)
setTargeting(slot, targeting)
setSizeMapping(slot, sizeMapping)

!shouldRefreshAds && window.Yieldbird.setGPTTargeting([slot])
window.googletag.enableServices()
Expand Down Expand Up @@ -151,51 +183,4 @@ export class AdManager {
}
})
}

private static createSlot(
adUnitPath: string,
size: googletag.GeneralSize,
optDiv: string
) {
let slot = window.googletag
.pubads()
.getSlots()
.find((el) => el.getSlotElementId() === optDiv)

slot =
slot ||
window.googletag
.defineSlot(adUnitPath, size, optDiv)
.addService(window.googletag.pubads())

slot.clearTargeting()

return slot
}

private static setTargeting(
slot: googletag.Slot,
targeting?: { [key: string]: googletag.NamedSize }
) {
if (slot && targeting) {
Object.keys(targeting).forEach((targetingKey: string) => {
slot.setTargeting(targetingKey, targeting[targetingKey])
})
}
}

private static setSizeMapping(
slot: googletag.Slot,
sizeMapping?: [googletag.SingleSizeArray, googletag.GeneralSize][]
) {
if (sizeMapping) {
const sizeMappingBuilder = window.googletag.sizeMapping()

sizeMapping.forEach((sizeMap) => {
sizeMappingBuilder.addSize(sizeMap[0], sizeMap[1])
})

slot.defineSizeMapping(sizeMappingBuilder.build())
}
}
}
20 changes: 20 additions & 0 deletions src/Utils/AdManagerUtils/createSlot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function createSlot(
adUnitPath: string,
size: googletag.GeneralSize,
optDiv: string
) {
let slot = window.googletag
.pubads()
.getSlots()
.find((el) => el.getSlotElementId() === optDiv)

slot =
slot ||
window.googletag
.defineSlot(adUnitPath, size, optDiv)
.addService(window.googletag.pubads())

slot.clearTargeting()

return slot
}
6 changes: 6 additions & 0 deletions src/Utils/AdManagerUtils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createSlot } from './createSlot'
import { initiaizeGlobalGPTOptions } from './initializeGlobalGPTOptions'
import { setTargeting } from './setTargeting'
import { setSizeMapping } from './setSizeMapping'

export { createSlot, initiaizeGlobalGPTOptions, setTargeting, setSizeMapping }
62 changes: 62 additions & 0 deletions src/Utils/AdManagerUtils/initializeGlobalGPTOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export function initiaizeGlobalGPTOptions(
collapseEmptyDivs?: boolean,
globalTargeting?: Record<string, string>,
onImpressionViewable?: (
event: googletag.events.ImpressionViewableEvent
) => void,
onSlotOnload?: (event: googletag.events.SlotOnloadEvent) => void,
onSlotRender?: (event: googletag.events.SlotRenderEndedEvent) => void,
onSlotRequested?: (event: googletag.events.SlotRequestedEvent) => void,
onSlotResponseReceived?: (
event: googletag.events.SlotResponseReceived
) => void,
onSlotVisibilityChanged?: (
event: googletag.events.SlotVisibilityChangedEvent
) => void
): void {
if (typeof window !== 'undefined') {
window.googletag.cmd.push(() => {
collapseEmptyDivs && window.googletag.pubads().collapseEmptyDivs()

if (globalTargeting && Object.keys(globalTargeting).length) {
Object.keys(globalTargeting).forEach((key) => {
window.googletag.pubads().setTargeting(key, globalTargeting[key])
})
}

if (onImpressionViewable) {
window.googletag
.pubads()
.addEventListener('impressionViewable', onImpressionViewable)
}

if (onSlotOnload) {
window.googletag.pubads().addEventListener('slotOnload', onSlotOnload)
}

if (onSlotRender) {
window.googletag
.pubads()
.addEventListener('slotRenderEnded', onSlotRender)
}

if (onSlotRequested) {
window.googletag
.pubads()
.addEventListener('slotRequested', onSlotRequested)
}

if (onSlotResponseReceived) {
window.googletag
.pubads()
.addEventListener('slotResponseReceived', onSlotResponseReceived)
}

if (onSlotVisibilityChanged) {
window.googletag
.pubads()
.addEventListener('slotVisibilityChanged', onSlotVisibilityChanged)
}
})
}
}
14 changes: 14 additions & 0 deletions src/Utils/AdManagerUtils/setSizeMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function setSizeMapping(
slot: googletag.Slot,
sizeMapping?: [googletag.SingleSizeArray, googletag.GeneralSize][]
) {
if (sizeMapping) {
const sizeMappingBuilder = window.googletag.sizeMapping()

sizeMapping.forEach((sizeMap) => {
sizeMappingBuilder.addSize(sizeMap[0], sizeMap[1])
})

slot.defineSizeMapping(sizeMappingBuilder.build())
}
}
Loading

0 comments on commit 1bfabf3

Please sign in to comment.