diff --git a/integrations/nielsen-dcr/lib/index.js b/integrations/nielsen-dcr/lib/index.js index 1bac8b54c..68e6de1d9 100644 --- a/integrations/nielsen-dcr/lib/index.js +++ b/integrations/nielsen-dcr/lib/index.js @@ -8,6 +8,7 @@ var integration = require('@segment/analytics.js-integration'); var find = require('obj-case').find; var reject = require('reject'); var dateformat = require('dateformat'); +var sha256 = require('js-sha256'); /** * Expose `NielsenDCR` integration. @@ -22,6 +23,7 @@ var NielsenDCR = (module.exports = integration('Nielsen DCR') .option('adAssetIdPropertyName', '') .option('subbrandPropertyName', '') .option('clientIdPropertyName', '') + .option('customSectionProperty', '') .option('sendCurrentTimeLivestream', false) .option('contentLengthPropertyName', 'total_length') .option('optout', false) @@ -100,10 +102,22 @@ NielsenDCR.prototype.loaded = function() { NielsenDCR.prototype.page = function(page) { var integrationOpts = page.options(this.name); + var customSectionName; + + //Allow customer to pick property to source section from otherwise fallback on page name + if (this.options.customSectionProperty) { + customSectionName = page.proxy( + 'properties.' + this.options.customSectionProperty + ); + } + var defaultSectionName = page.fullName() || page.name() || page.event(); + var sectionName = customSectionName || defaultSectionName; + var url = page.url(); + var staticMetadata = reject({ type: 'static', - assetid: page.url(), // *DYNAMIC METADATA*: unique ID for each article **REQUIRED** - section: page.fullName() || page.name() || page.event(), // *DYNAMIC METADATA*: section of site **REQUIRED** + assetid: sha256(url), // *DYNAMIC METADATA*: unique ID for each article, deterministic SHA256 hash of url since assetid cannot contain special characters **REQUIRED** + section: sectionName, // *DYNAMIC METADATA*: section of site **REQUIRED** segA: integrationOpts.segA, // *DYNAMIC METADATA*: custom segment segB: integrationOpts.segB, // *DYNAMIC METADATA*: custom segment segC: integrationOpts.segC // *DYNAMIC METADATA*: custom segment @@ -191,7 +205,7 @@ NielsenDCR.prototype.getContentMetadata = function(track, type) { hasAds: find(integrationOpts, 'hasAds') === true ? '1' : '0' }; - if (this.options.contentLengthPropertyName !== 'total_length') { + if (this.options.contentLengthPropertyName && this.options.contentLengthPropertyName !== 'total_length') { var contentLengthKey = this.options.contentLengthPropertyName; contentMetadata.length = track.proxy(propertiesPath + contentLengthKey); } else { @@ -394,11 +408,13 @@ NielsenDCR.prototype.videoAdCompleted = function(track) { * Video Playback Paused * Video Playback Seek Started * Video Playback Buffer Started + * Video Playback Interrupted + * Video Playback Exited * * @api public */ -NielsenDCR.prototype.videoPlaybackPaused = NielsenDCR.prototype.videoPlaybackSeekStarted = NielsenDCR.prototype.videoPlaybackBufferStarted = function( +NielsenDCR.prototype.videoPlaybackPaused = NielsenDCR.prototype.videoPlaybackSeekStarted = NielsenDCR.prototype.videoPlaybackBufferStarted = NielsenDCR.prototype.videoPlaybackInterrupted = NielsenDCR.prototype.videoPlaybackExited = function( track ) { var self = this; @@ -460,13 +476,12 @@ NielsenDCR.prototype.videoPlaybackResumed = NielsenDCR.prototype.videoPlaybackSe /** * Video Playback Completed - * Video Playback Interrupted * * * @api public */ -NielsenDCR.prototype.videoPlaybackCompleted = NielsenDCR.prototype.videoPlaybackInterrupted = function( +NielsenDCR.prototype.videoPlaybackCompleted = function( track ) { var self = this; @@ -481,7 +496,7 @@ NielsenDCR.prototype.videoPlaybackCompleted = NielsenDCR.prototype.videoPlayback this._client.ggPM('setPlayheadPosition', position); this._client.ggPM('end', position); - // reset state because "Video Playback Completed/Interrupted" are "non-recoverable events" + // reset state because "Video Playback Completed" are "non-recoverable events" // e.g. they should always be followed by the start of a new video session with either // "Video Content Started" or "Video Ad Started" events this.currentPosition = 0; diff --git a/integrations/nielsen-dcr/package.json b/integrations/nielsen-dcr/package.json index 931234b7d..34af36a0d 100644 --- a/integrations/nielsen-dcr/package.json +++ b/integrations/nielsen-dcr/package.json @@ -1,7 +1,7 @@ { "name": "@segment/analytics.js-integration-nielsen-dcr", "description": "The Nielsen DCR analytics.js integration.", - "version": "2.0.2", + "version": "2.1.1", "keywords": [ "analytics.js", "analytics.js-integration", diff --git a/integrations/nielsen-dcr/test/index.test.js b/integrations/nielsen-dcr/test/index.test.js index 4c877d5bc..f205e378d 100644 --- a/integrations/nielsen-dcr/test/index.test.js +++ b/integrations/nielsen-dcr/test/index.test.js @@ -41,6 +41,7 @@ describe('NielsenDCR', function() { .option('adAssetIdPropertyName', '') .option('subbrandPropertyName', '') .option('clientIdPropertyName', '') + .option('customSectionProperty', '') .option('contentLengthPropertyName', 'total_length') .option('optout', false) .option('sendCurrentTimeLivestream', false) @@ -90,7 +91,7 @@ describe('NielsenDCR', function() { analytics.page(); var staticMetadata = { type: 'static', - assetid: window.location.href, + assetid: 'ff4c0efe94509b3d21872f0c0bfec92faaed5ae46d707b6ea832a74f9f1fe38d', section: 'Loaded a Page' }; analytics.called( @@ -99,6 +100,29 @@ describe('NielsenDCR', function() { staticMetadata ); }); + + it('should send static metadata with custom section name', function() { + var props; + props = { + custom_section_name_prop: 'Custom Page Name' + } + + nielsenDCR.options.customSectionProperty = + 'custom_section_name_prop'; + + analytics.page('Homepage', props); + + var staticMetadata = { + type: 'static', + assetid: 'ff4c0efe94509b3d21872f0c0bfec92faaed5ae46d707b6ea832a74f9f1fe38d', + section: 'Custom Page Name' + }; + analytics.called( + nielsenDCR._client.ggPM, + 'staticstart', + staticMetadata + ); + }); }); describe('#track', function() { @@ -183,13 +207,19 @@ describe('NielsenDCR', function() { it('video playback interrupted during content', function() { analytics.track('Video Playback Interrupted', props); analytics.called(window.clearInterval); - analytics.called(nielsenDCR._client.ggPM, 'end', props.position); + analytics.called(nielsenDCR._client.ggPM, 'stop', props.position); }); it('video playback interrupted during ad', function() { analytics.track('Video Playback Interrupted', props); analytics.called(window.clearInterval); - analytics.called(nielsenDCR._client.ggPM, 'end', props.position); + analytics.called(nielsenDCR._client.ggPM, 'stop', props.position); + }); + + it('video playback exited', function() { + analytics.track('Video Playback Exited', props); + analytics.called(window.clearInterval); + analytics.called(nielsenDCR._client.ggPM, 'stop', props.position); }); it('video playback completed', function() { diff --git a/integrations/nielsen-dtvr/lib/index.js b/integrations/nielsen-dtvr/lib/index.js index ccb5bb40f..965d18b89 100644 --- a/integrations/nielsen-dtvr/lib/index.js +++ b/integrations/nielsen-dtvr/lib/index.js @@ -105,25 +105,15 @@ NielsenDTVR.prototype.track = function(track) { */ NielsenDTVR.prototype.videoContentStarted = function(track) { - var date; - var time; var metadata; - // Proactively ensure that we call "end" whenever new content + // Proactively ensure we clear the session whenever new content // starts. Here, we'll catch it if a customer forgets to call a Segment - // "Completed" event, so we'll end the video for them. `end` is also - // appropriate during a video "interruption", + // "Completed" event, so we'll clear the ID3 tags and stream. // e.g. if a user is alternating b/w watching two videos on the same page. if (this.previousEvent && track !== this.previousEvent) { - date = this.previousEvent.timestamp(); - if ( - this.previousEvent.proxy('properties.livestream') === true && - date instanceof Date - ) { - time = Math.floor(date.getTime() / 1000); - } else if (this.previousEvent.proxy('properties.position')) { - time = this.previousEvent.proxy('properties.position'); - } - this.client.ggPM('end', time); + this.ID3 = null; + this.previousEvent = null; + this.isDTVRStream = null; } metadata = this.mapMetadata(track); @@ -136,20 +126,28 @@ NielsenDTVR.prototype.videoContentStarted = function(track) { }; /** + * These are considered non-recoverable completion scenarios. + * Nielsen has requested we do not fire anything for these events. + * We will simply reset ID3 tags and clear out the stream/session. + * * Video Content Completed * Video Playback Completed + * Video Playback Exited * * @api public */ -NielsenDTVR.prototype.videoContentCompleted = NielsenDTVR.prototype.videoPlaybackCompleted = function( - track -) { +NielsenDTVR.prototype.videoContentCompleted = NielsenDTVR.prototype.videoPlaybackCompleted = NielsenDTVR.prototype.videoPlaybackExited = function() { if (!this.isDTVRStream) return; - this.end(track); + this.ID3 = null; + this.previousEvent = null; + this.isDTVRStream = null; }; /** + * These are considered recoverable interruption scenarios. + * Nielsen has requested we do not fire anything for these events, aside from reporting the latest ID3 tag. + * * Video Playback Interrupted * Video Playback Seek Started * Video Playback Buffer Started @@ -163,7 +161,6 @@ NielsenDTVR.prototype.videoPlaybackInterrupted = NielsenDTVR.prototype.videoPlay ) { if (!this.isDTVRStream) return; this.sendID3(track); - this.end(track, 'recoverable'); }; /** @@ -217,33 +214,6 @@ NielsenDTVR.prototype.sendID3 = function(event) { } }; -/** - * End playback - * - * @api private - */ - -NielsenDTVR.prototype.end = function(event, interruptType) { - var livestream = event.proxy('properties.livestream'); - var position = event.proxy('properties.position'); - var time; - if (livestream) { - time = Math.floor(event.timestamp().getTime() / 1000); - } else if (position) { - time = position; - } - - if (time) { - this.client.ggPM('end', time); - } - - if (interruptType !== 'recoverable') { - this.ID3 = null; - this.previousEvent = null; - this.isDTVRStream = null; - } -}; - /** * Helper to validate that metadata contains required properties, i.e. * all values are truthy Strings. We don't need to validate keys b/c diff --git a/integrations/nielsen-dtvr/package.json b/integrations/nielsen-dtvr/package.json index 7673c4b57..d0ee7ec1d 100644 --- a/integrations/nielsen-dtvr/package.json +++ b/integrations/nielsen-dtvr/package.json @@ -1,7 +1,7 @@ { "name": "@segment/analytics.js-integration-nielsen-dtvr", "description": "The Nielsen DTVR analytics.js integration.", - "version": "1.0.1", + "version": "1.0.2", "keywords": [ "analytics.js", "analytics.js-integration", diff --git a/integrations/nielsen-dtvr/test/index.test.js b/integrations/nielsen-dtvr/test/index.test.js index edfd99a94..26820531e 100644 --- a/integrations/nielsen-dtvr/test/index.test.js +++ b/integrations/nielsen-dtvr/test/index.test.js @@ -128,7 +128,6 @@ describe('NielsenDTVR', function() { analytics.track('Video Playback Interrupted', props); analytics.called(nielsenDTVR.client.ggPM, 'sendID3', props.custom); - analytics.called(nielsenDTVR.client.ggPM, 'end', props.position); }); }); @@ -164,13 +163,11 @@ describe('NielsenDTVR', function() { it('should send video playback buffer started', function() { analytics.track('Video Playback Buffer Started', props); analytics.called(nielsenDTVR.client.ggPM, 'sendID3', props.id3); - analytics.called(nielsenDTVR.client.ggPM, 'end', props.position); }); it('should send video playback interrupted', function() { analytics.track('Video Playback Interrupted', props); analytics.called(nielsenDTVR.client.ggPM, 'sendID3', props.id3); - analytics.called(nielsenDTVR.client.ggPM, 'end', props.position); }); it('should send video playback resumed', function() { @@ -186,7 +183,6 @@ describe('NielsenDTVR', function() { it('should send video playback seek started', function() { analytics.track('Video Playback Seek Started', props); analytics.called(nielsenDTVR.client.ggPM, 'sendID3', props.id3); - analytics.called(nielsenDTVR.client.ggPM, 'end', props.position); }); it('should send video playback seek completed', function() { @@ -201,20 +197,10 @@ describe('NielsenDTVR', function() { it('should send video playback completed', function() { analytics.track('Video Playback Completed', props); - analytics.called(nielsenDTVR.client.ggPM, 'end', props.position); }); - it('should send video playback completed livestream', function() { - props.livestream = true; - - var timestamp = new Date(); - // when live streams end, we need to pass the Unix timestamp in seconds per Nielsen - var unixTime = Math.floor(timestamp.getTime() / 1000); - - analytics.track('Video Playback Completed', props, { - timestamp: timestamp - }); - analytics.called(nielsenDTVR.client.ggPM, 'end', unixTime); + it('should send video playback exited', function() { + analytics.track('Video Playback Exited', props); }); }); @@ -234,7 +220,6 @@ describe('NielsenDTVR', function() { it('should send video content completed', function() { analytics.track('Video Content Completed', props); - analytics.called(nielsenDTVR.client.ggPM, 'end', props.position); }); it('should send video content started', function() { @@ -257,27 +242,6 @@ describe('NielsenDTVR', function() { }); analytics.didNotCall(nielsenDTVR.client.ggPM, 'sendID3', props.id3); }); - - it('should call end before starting a new content stream if the previous stream was not ended correctly', function() { - var previousEvent = { - asset_id: '123', - ad_asset_id: null, - channel: 'segment', - load_type: 'linear', - position: 1, - id3: '1', - livestream: true - }; - var currentEvent = props; - var timestamp = new Date(); - // when live streams end, we need to pass the Unix timestamp in seconds per Nielsen - var unixTime = Math.floor(timestamp.getTime() / 1000); - analytics.track('Video Content Started', previousEvent, { - timestamp: timestamp - }); - analytics.track('Video Content Started', currentEvent); - analytics.called(nielsenDTVR.client.ggPM, 'end', unixTime); - }); }); describe('#persisted data', function() { diff --git a/yarn.lock b/yarn.lock index 40e2d0680..854c533bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2854,9 +2854,9 @@ analytics-events@^1.2.0: integrity sha1-sDCnqCv+tF7w2qdNIM+ai6TUvcs= analytics-events@^2.0.2, analytics-events@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/analytics-events/-/analytics-events-2.2.0.tgz#f00f55946940a6357809582f6fded6ab80034b84" - integrity sha1-8A9VlGlApjV4CVgvb97Wq4ADS4Q= + version "2.2.5" + resolved "https://registry.npmjs.org/analytics-events/-/analytics-events-2.2.5.tgz#7f20217a7b08d3bf719e48b62b0785064e74f71b" + integrity sha512-WuuEs52q/mxvM/wvLSdNA71iEqwTSnLodkwnPpzVUYzFuoR1YOujvN09ZMpkcaVhkTBK/rbfPiSpDEDD8FFD6Q== dependencies: "@ndhoule/foldl" "^2.0.1" "@ndhoule/map" "^2.0.1"