Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions integrations/nielsen-dcr/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion integrations/nielsen-dcr/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
36 changes: 33 additions & 3 deletions integrations/nielsen-dcr/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand All @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down
64 changes: 17 additions & 47 deletions integrations/nielsen-dtvr/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -163,7 +161,6 @@ NielsenDTVR.prototype.videoPlaybackInterrupted = NielsenDTVR.prototype.videoPlay
) {
if (!this.isDTVRStream) return;
this.sendID3(track);
this.end(track, 'recoverable');
};

/**
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion integrations/nielsen-dtvr/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
40 changes: 2 additions & 38 deletions integrations/nielsen-dtvr/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down Expand Up @@ -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() {
Expand All @@ -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() {
Expand All @@ -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);
});
});

Expand All @@ -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() {
Expand All @@ -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() {
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down