diff --git a/elements/bulbs-video/components/revealed.js b/elements/bulbs-video/components/revealed.js index 217f56fd..61fc34d6 100644 --- a/elements/bulbs-video/components/revealed.js +++ b/elements/bulbs-video/components/revealed.js @@ -10,7 +10,7 @@ import React, { PropTypes } from 'react'; import invariant from 'invariant'; // FIXME: where should this be defined? Per-app? -// Or in some better sort of settings file here? +// or in some better sort of settings file here? global.BULBS_ELEMENTS_ONIONSTUDIOS_GA_ID = 'UA-223393-14'; global.BULBS_ELEMENTS_COMSCORE_ID = '6036328'; @@ -40,6 +40,21 @@ export default class Revealed extends React.Component { '`` requires `jwplayer` to be in global scope.' ); + invariant( + window.isMobile, + '`` requires `isMobile()` to be set on window.' + ); + + invariant( + window.FREEWHEEL_NETWORK_ID, + '`` requires `FREEWHEEL_NETWORK_ID` to be set on window.' + ); + + invariant( + window.FREEWHEEL_NETWORK_HASH, + '`` requires `FREEWHEEL_NETWORK_HASH` to be set on window.' + ); + let gaPrefix = makeGaPrefix(); ga('create', BULBS_ELEMENTS_ONIONSTUDIOS_GA_ID, 'auto', { name: gaPrefix }); @@ -146,7 +161,7 @@ export default class Revealed extends React.Component { vastTest (searchString) { // eslint-disable-line consistent-return if (searchString !== '') { - let vastId = this.parseParam('xgid', searchString); + let vastId = this.parseParam('apid', searchString); if (vastId) { return vastId; @@ -156,30 +171,94 @@ export default class Revealed extends React.Component { return false; } - vastUrl (videoMeta) { - let baseUrl = 'http://us-theonion.videoplaza.tv/proxy/distributor/v2?rt=vast_2.0'; - - let vastTestId = this.vastTest(window.location.search); + getProfValue () { + let prof; + if (window.isMobile.any) { + prof = 'theonion_mobileweb_html5'; + } + else { + prof = 'theonion_desktop_html5'; + } + return prof; + } - // AD_TYPE: one of p (preroll), m (midroll), po (postroll), o (overlay) - baseUrl += '&tt=p'; - videoMeta.tags.push('html5'); // Force HTML 5 - // Tags - baseUrl += '&t=' + videoMeta.tags; - //Category - let hostChannel = videoMeta.hostChannel; - let channel = videoMeta.channel_slug; - let series = videoMeta.series_slug; - let category = `${hostChannel}/${channel}`; - if (series) { - category += `/${series}`; + getDeviceAcronym () { + let deviceAcronym; + if (window.isMobile.any) { + deviceAcronym = 'm'; + } + else { + deviceAcronym = 'd'; } - baseUrl += '&s=' + category; - baseUrl += '&rnd=' + this.cacheBuster(); + return deviceAcronym; + } + + getSiteName () { + let targetChannel = this.props.video.targeting.target_channel; + return targetChannel.replace(/-/g, ''); + } - if (vastTestId) { - baseUrl += '&xgid=' + vastTestId; + getDfpSection () { + let dfpSection; + if (window.TARGETING.dfp_section) { + dfpSection = window.TARGETING.dfp_section; + } + else if (window.TARGETING.dfp_specialcoverage) { + let slug = window.TARGETING.dfp_specialcoverage; + dfpSection = `specialcoverage_${slug}`; + } + else { + dfpSection = 'video'; } + return dfpSection; + } + + buildCustomSiteSectionId (hostChannel) { + let deviceAcronym = this.getDeviceAcronym(); + let siteName = this.getSiteName(); + let siteSection = this.getDfpSection(); + + return `${deviceAcronym}.${siteName}_${siteSection}_${hostChannel}`; + } + + buildCustomContentVideoAssetId (videohubReferenceId) { + return `onion_${videohubReferenceId}`; + } + + vastUrl (videoMeta) { + let hostChannel = videoMeta.hostChannel; + let videohubReferenceId = videoMeta.id; + let randomVideoPlayerNumber = videoMeta.vprn; + let vastTestId = this.vastTest(window.location.search); + let series = videoMeta.series_slug; + let campaignId = this.props.targetCampaignId; + let specialCoverage = this.props.targetSpecialCoverage; + + let baseUrl = `http://${window.FREEWHEEL_NETWORK_HASH}.v.fwmrm.net/ad/g/1?`; + + // required global params + baseUrl += 'nw=' + `${window.FREEWHEEL_NETWORK_ID}`; + baseUrl += '&resp=' + 'vmap1'; + baseUrl += '&prof=' + this.getProfValue(); + baseUrl += '&csid=' + this.buildCustomSiteSectionId(hostChannel); + baseUrl += '&caid=' + this.buildCustomContentVideoAssetId(videohubReferenceId); + baseUrl += '&pvrn=' + this.cacheBuster(); + baseUrl += '&vprn=' + randomVideoPlayerNumber; + + // optional global param + if (vastTestId) { baseUrl += '&cana=' + vastTestId; } + + // Key Values + baseUrl += ';video_id=' + videohubReferenceId; + baseUrl += '&channel_slug=' + videoMeta.channel_slug; + if (series) { baseUrl += '&series_slug=' + series; } + if (campaignId) { baseUrl += '&campaign_id=' + campaignId; } + if (specialCoverage) { baseUrl += '&special_coverage=' + specialCoverage; } + + // Slot Params *Required Fields* + baseUrl += ';slid=' + 'Preroll'; + baseUrl += '&tpcl=' + 'PREROLL'; + baseUrl += '&ptgt=' + 'a'; return baseUrl; } @@ -205,6 +284,14 @@ export default class Revealed extends React.Component { element.id = videoMeta.gaPrefix; let player = global.jwplayer(element); + // random video player number ending with video id + // to be used in vastUrl query string + let randomVideoPlayerNumber = parseInt( + `${this.cacheBuster()}${videoMeta.id}`, + 10 + ); + videoMeta.vprn = randomVideoPlayerNumber; + player.videoMeta = videoMeta; let playerOptions = { diff --git a/elements/bulbs-video/components/revealed.test.js b/elements/bulbs-video/components/revealed.test.js index 88d9ee8f..9d242866 100644 --- a/elements/bulbs-video/components/revealed.test.js +++ b/elements/bulbs-video/components/revealed.test.js @@ -1,4 +1,5 @@ /* eslint-disable max-len */ +/* eslint-disable no-unused-vars */ import React, { PropTypes } from 'react'; import { shallow } from 'enzyme'; @@ -94,6 +95,8 @@ describe(' ', () => { describe('componentDidMount globalsCheck', () => { global.BULBS_ELEMENTS_ONIONSTUDIOS_GA_ID = 'a-ga-id'; + window.isMobile = () => {}; + window.isMobile.any = false; global.ga = () => {}; const globals = [ @@ -199,6 +202,8 @@ describe(' ', () => { state = {}; global.BULBS_ELEMENTS_ONIONSTUDIOS_GA_ID = 'a-ga-id'; global.ga = sinon.spy(); + window.FREEWHEEL_NETWORK_ID = '12345'; + window.FREEWHEEL_NETWORK_HASH = '1a345'; makeVideoPlayerSpy = sinon.spy(); }); @@ -529,61 +534,245 @@ describe(' ', () => { }); }); + describe('getProfValue', () => { + it('returns different values on desktop and mobile', () => { + // desktop + window.isMobile.any = false; + let response = Revealed.prototype.getProfValue.call(); + expect(response).to.equal('theonion_desktop_html5'); + + // mobile + window.isMobile.any = true; + response = Revealed.prototype.getProfValue.call(); + expect(response).to.equal('theonion_mobileweb_html5'); + }); + }); + + describe('#getSiteName', () => { + let props; + let response; + + beforeEach(() => { + props = { video: { targeting: { target_channel: 'avclub' } } }; + }); + + it('pulls site name from props.video.targeting.target_channel', () => { + response = Revealed.prototype.getSiteName.call({ props }); + expect(response).to.eql(props.video.targeting.target_channel); + }); + it('it removes hypens from props.video.targeting.target_channel', () => { + props.video.targeting.target_channel = 'the-freakin-onion'; + response = Revealed.prototype.getSiteName.call({ props }); + expect(response).to.eql('thefreakinonion'); + }); + }); + + describe('buildCustomSiteSectionId', () => { + let response; + let getSiteNameStub; + let getDeviceAcronymStub; + let getDfpSectionStub; + + beforeEach(() => { + getSiteNameStub = sinon.stub().returns('website'); + getDeviceAcronymStub = sinon.stub().returns('d'); + getDfpSectionStub = sinon.stub().returns('fun'); + response = Revealed.prototype.buildCustomSiteSectionId.call({ + getSiteName: getSiteNameStub, + getDeviceAcronym: getDeviceAcronymStub, + getDfpSection: getDfpSectionStub, + }, 'host_channel'); + }); + + // csid format: .__ + // example: d.theonion_specialcoverage_boldness_main + it('#getDeviceAcronym device acronym ', () => { + // desktop + window.isMobile.any = false; + response = Revealed.prototype.getDeviceAcronym.call(); + expect(response).to.equal('d'); + + // mobile + window.isMobile.any = true; + response = Revealed.prototype.getDeviceAcronym.call(); + expect(response).to.equal('m'); + }); + + it('includes site name', () => { + expect(response.indexOf('website') !== -1).to.be.true; + }); + + context('getDfpSection', () => { + it('window.TARGETING.dfp_section is set', () => { + window.TARGETING = { dfp_section: 'sunshine' }; + response = Revealed.prototype.getDfpSection.call(); + expect(response).to.eql(window.TARGETING.dfp_section); + }); + + it('special coverage page', () => { + window.TARGETING = { dfp_specialcoverage: 'forest-walk' }; + response = Revealed.prototype.getDfpSection.call(); + let expected = `specialcoverage_${window.TARGETING.dfp_specialcoverage}`; + expect(response).to.eql(expected); + }); + + it('not special coverage or dfp_section', () => { + window.TARGETING = {}; + response = Revealed.prototype.getDfpSection.call(); + expect(response).to.eql('video'); + }); + }); + + it('populates csid', () => { + response = Revealed.prototype.buildCustomSiteSectionId.call({ + getSiteName: getSiteNameStub, + getDeviceAcronym: getDeviceAcronymStub, + getDfpSection: getDfpSectionStub, + }, 'host_channel'); + let expected = 'd.website_fun_host_channel'; + expect(response).to.eql(expected); + }); + }); + + describe('buildCustomContentVideoAssetId', () => { + it('sets to onion studios id', () => { + let videohubReferenceId = 1234; + let response = Revealed.prototype.buildCustomContentVideoAssetId.call({}, videohubReferenceId); + expect(response).to.eql('onion_1234'); + }); + }); + describe('vastUrl', () => { let videoMeta; let cacheBusterStub; let vastTestStub; + let vastUrl; + let getProfValueStub; + let getSiteNameStub; // eslint-disable no-unused-vars + let parsed; + let buildCustomSiteSectionIdStub; + let buildCustomContentVideoAssetIdStub; + let queryKeys; - context('default', () => { - beforeEach(() => { - cacheBusterStub = sinon.stub().returns('456'); - vastTestStub = sinon.stub().returns(null); - videoMeta = { - tags: ['clickhole', 'main', '12345'], - category: 'main/clickhole', - channel_slug: 'channel_slug', - hostChannel: 'host_channel', - }; - }); + beforeEach(() => { + cacheBusterStub = sinon.stub().returns(456); + vastTestStub = sinon.stub().returns(5678); + getProfValueStub = sinon.stub().returns('testy'); + getSiteNameStub = sinon.stub().returns('website'); + buildCustomSiteSectionIdStub = sinon.stub().returns('d.website_camping_channel'); + buildCustomContentVideoAssetIdStub = sinon.stub().returns('onion_1234'); + videoMeta = { + id: 765, + vprn: 456765, + tags: ['clickhole', 'main', '12345'], + category: 'main/clickhole', + channel_slug: 'channel_slug', + hostChannel: 'host_channel', + series_slug: 'series-slug', + }; + vastUrl = Revealed.prototype.vastUrl.call({ + props: { + targetCampaignId: 66, + targetSpecialCoverage: 'blooping', + }, + cacheBuster: cacheBusterStub, + vastTest: vastTestStub, + getProfValue: getProfValueStub, + buildCustomSiteSectionId: buildCustomSiteSectionIdStub, + buildCustomContentVideoAssetId: buildCustomContentVideoAssetIdStub, + }, videoMeta); + parsed = url.parse(vastUrl, true); + }); - it('returns the vast url', function () { - let vastUrl = Revealed.prototype.vastUrl.call({ - cacheBuster: cacheBusterStub, - vastTest: vastTestStub, - }, videoMeta); - let parsed = url.parse(vastUrl, true); - expect(parsed.protocol).to.eql('http:'); - expect(parsed.host).to.eql('us-theonion.videoplaza.tv'); - expect(parsed.pathname).to.eql('/proxy/distributor/v2'); - expect(Object.keys(parsed.query)).to.eql(['rt', 'tt', 't', 's', 'rnd']); - expect(parsed.query.rt).to.eql('vast_2.0'); - expect(parsed.query.tt).to.eql('p'); - expect(parsed.query.t).to.eql('clickhole,main,12345,html5'); - expect(parsed.query.s).to.eql('host_channel/channel_slug'); - expect(parsed.query.rnd).to.eql('456'); - }); + it('sets the http: protocol', function () { + expect(parsed.protocol).to.eql('http:'); + }); + it('sets host', function () { + expect(parsed.host).to.eql('1a345.v.fwmrm.net'); + }); + it('sets pathname', function () { + expect(parsed.pathname).to.eql('/ad/g/1'); + }); + it('sets nw', function () { + expect(parsed.query.nw).to.eql('12345'); + }); + it('sets resp', function () { + expect(parsed.query.resp).to.eql('vmap1'); + }); + it('sets prof', function () { + expect(parsed.query.prof).to.eql('testy'); + }); + it('sets csid', function () { + expect(parsed.query.csid).to.eql('d.website_camping_channel'); + }); + it('sets caid', function () { + expect(parsed.query.caid).to.eql('onion_1234'); + }); + it('sets pvrn', function () { + expect(parsed.query.pvrn).to.eql('456'); + }); + it('sets vprn', function () { + expect(parsed.query.vprn).to.eql('456765'); + }); + it('sets cana', function () { + let canaIndex = parsed.search.indexOf('cana=5678'); + expect(canaIndex !== -1).to.be.true; + }); + it('sets video_id', function () { + let vidoeIdIndex = parsed.search.indexOf('video_id=765'); + expect(vidoeIdIndex !== -1).to.be.true; + }); + it('sets channel_slug', function () { + expect(parsed.query.channel_slug).to.eql('channel_slug'); + }); + it('sets series_slug', function () { + expect(parsed.query.series_slug).to.eql('series-slug'); + }); + it('sets campaign_id', function () { + expect(parsed.query.campaign_id).to.eql('66'); + }); + it('sets special_coverage', function () { + let specCovIndex = parsed.search.indexOf('special_coverage=blooping'); + expect(specCovIndex !== -1).to.be.true; + }); + it('sets slid', function () { + let slidIndex = parsed.search.indexOf('slid=Preroll'); + expect(slidIndex !== -1).to.be.true; + }); + it('sets tpcl', function () { + expect(parsed.query.tpcl).to.eql('PREROLL'); + }); + it('sets ptgt', function () { + expect(parsed.query.ptgt).to.eql('a'); }); - context('when series_slug is given', () => { + context('conditionally set parameters', () => { beforeEach(() => { - cacheBusterStub = sinon.stub().returns('456'); vastTestStub = sinon.stub().returns(null); - videoMeta = { - tags: ['clickhole', 'main', '12345'], - category: 'main/clickhole', - channel_slug: 'channel_slug', - series_slug: 'series_slug', - hostChannel: 'host_channel', - }; - }); - - it('returns the vast url', function () { - let vastUrl = Revealed.prototype.vastUrl.call({ + videoMeta.series_slug = null; + vastUrl = Revealed.prototype.vastUrl.call({ + props: {}, cacheBuster: cacheBusterStub, vastTest: vastTestStub, + getProfValue: getProfValueStub, + buildCustomSiteSectionId: buildCustomSiteSectionIdStub, + buildCustomContentVideoAssetId: buildCustomContentVideoAssetIdStub, }, videoMeta); - let parsed = url.parse(vastUrl, true); - expect(parsed.query.s).to.eql('host_channel/channel_slug/series_slug'); + parsed = url.parse(vastUrl, true); + queryKeys = Object.keys(parsed.query); + }); + + it('null vastTestId cana not populated', () => { + expect(queryKeys.indexOf('cana') === -1).to.be.true; + }); + it('no series_slug series_slug not populated', () => { + expect(queryKeys.indexOf('series_slug') === -1).to.be.true; + }); + it('no targetCampaignId campaign_id not populated', () => { + expect(queryKeys.indexOf('campaign_id') === -1).to.be.true; + }); + it('no targetSpecialCoverage special_coverage not populated', () => { + expect(queryKeys.indexOf('special_coverage') === -1).to.be.true; }); }); }); @@ -593,6 +782,7 @@ describe(' ', () => { let element; let player; let videoMeta; + let cacheBusterStub; beforeEach(() => { element = {}; @@ -658,6 +848,7 @@ describe(' ', () => { gaPrefix: 'videoplayer0', }); playerSetup = sinon.spy(); + cacheBusterStub = sinon.stub().returns('456'); player = { setup: playerSetup, }; @@ -690,6 +881,7 @@ describe(' ', () => { props: {}, extractSources: extractSourcesStub, vastUrl: vastUrlStub, + cacheBuster: cacheBusterStub, extractTrackCaptions: extractTrackCaptionsStub, }, element, videoMeta); }); @@ -763,6 +955,7 @@ describe(' ', () => { props: {}, extractSources: extractSourcesStub, vastUrl: vastUrlStub, + cacheBuster: cacheBusterStub, extractTrackCaptions: extractCaptionsStub, }, element, videoMeta); }); @@ -791,6 +984,7 @@ describe(' ', () => { props: { disableSharing: true }, extractSources: extractSourcesStub, vastUrl: vastUrlStub, + cacheBuster: cacheBusterStub, extractTrackCaptions: extractTrackCaptionsStub, }, element, videoMeta); }); @@ -820,6 +1014,7 @@ describe(' ', () => { extractSources: extractSourcesStub, extractTrackCaptions: extractTrackCaptionsStub, vastUrl: vastUrlStub, + cacheBuster: cacheBusterStub, }, element, videoMeta); });