diff --git a/integrations/adobe-analytics/HISTORY.md b/integrations/adobe-analytics/HISTORY.md index e16b2a8d4..4d81820a2 100644 --- a/integrations/adobe-analytics/HISTORY.md +++ b/integrations/adobe-analytics/HISTORY.md @@ -1,3 +1,9 @@ +1.16.2 / 2020-07-13 +=================== +* Reads session playhead values from `window._segHBPlayheads` if it exists. This solves an issue where the playhead is only updated when 'Video Content Playing' (+ various others) events are tracked. To get around this, we allow video implementations to set the playhead value as often as possible without the need to trigger an event. +* Removes trackComplete from Video Content Completed events and only calls chapterComplete on these events. It also adds trackComplete to Video Playback Completed events. This is in line with Adobe's documentation and also allows for parity between iOS, a.js, and Android. +* Stringifies context data values which are booleans. The AA SDK tends to reject false boolean values when setting them on the window object. This does not break existing behavior since booleans are stringified when they're sent in the query string. + 1.16.1 / 2020-05-15 =================== * Supports sending top level `event` as `prop`, `eVar`, `lVar`, `hVar`, or Context Data Variable. diff --git a/integrations/adobe-analytics/lib/index.js b/integrations/adobe-analytics/lib/index.js index 84073d524..dbd35942d 100644 --- a/integrations/adobe-analytics/lib/index.js +++ b/integrations/adobe-analytics/lib/index.js @@ -135,6 +135,10 @@ AdobeAnalytics.prototype.initialize = function() { // In case this has been defined already window.s_account = window.s_account || options.reportSuiteId; + // Initialize a window object that can be used to update the playhead value of a session + // WITHOUT sending several 'Video Content Playing' events. (see line 1242) + window._segHBPlayheads = {}; + // Load the larger Heartbeat script only if the customer has it enabled in settings. // This file is considerably bigger, so this check is necessary. if (options.heartbeatTrackingServerUrl) { @@ -620,6 +624,14 @@ function updateContextData(facade, options) { return; } + // If context data values are booleans then stringify them. + // Adobe's SDK seems to reject a false boolean value. Stringifying is + // acceptable since these values are appended as query strings anyway. + if (typeof value === 'boolean') { + addContextDatum(key, value.toString()); + return; + } + addContextDatum(key, value); }, contextProperties); } @@ -1236,7 +1248,17 @@ function initHeartbeat(track) { mediaHeartbeatConfig.debugLogging = !!window._enableHeartbeatDebugLogging; // Optional beta flag for seeing debug output. mediaHeartbeatDelegate.getCurrentPlaybackTime = function() { - return self.playhead || 0; // TODO: Bind to the Heartbeat events we have specced. + var playhead = self.playhead || 0; + + // We allow implementions to set the playhead value of a video session on a shared + // window object. This allows us to relay the playhead to AA's heartbeat SDK several + // times a second, without relying on a 'Video Content Playing' event to update the position. + var sessions = window._segHBPlayheads || {}; + if (sessions[props.session_id]) { + playhead = sessions[props.session_id]; + } + + return playhead; }; mediaHeartbeatDelegate.getQoSObject = function() { @@ -1317,9 +1339,7 @@ function heartbeatVideoStart(track) { chapterObj, chapterCustomMetadata ); - this.mediaHeartbeats[ - props.session_id || 'default' - ].chapterInProgress = true; + this.mediaHeartbeats[props.session_id || 'default'].chapterInProgress = true; } } @@ -1332,8 +1352,6 @@ function heartbeatVideoComplete(track) { videoAnalytics.MediaHeartbeat.Event.ChapterComplete ); this.mediaHeartbeats[props.session_id || 'default'].chapterInProgress = false; - - this.mediaHeartbeats[props.session_id || 'default'].heartbeat.trackComplete(); } function heartbeatVideoPaused(track) { @@ -1347,9 +1365,8 @@ function heartbeatSessionEnd(track) { populateHeartbeat.call(this, track); var props = track.properties(); - this.mediaHeartbeats[ - props.session_id || 'default' - ].heartbeat.trackSessionEnd(); + this.mediaHeartbeats[props.session_id || 'default'].heartbeat.trackComplete(); + this.mediaHeartbeats[props.session_id || 'default'].heartbeat.trackSessionEnd(); // Remove the session from memory when it's all over. delete this.mediaHeartbeats[props.session_id || 'default']; @@ -1514,6 +1531,15 @@ function createCustomVideoMetadataContext(track, options) { if (!key || value === undefined || value === null || value === '') { return; } + + // If context data values are booleans then stringify them. + // Adobe's SDK seems to reject a false boolean value. Stringifying is + // acceptable since these values are appended as query strings anyway. + if (typeof value === 'boolean') { + contextData[key] = value.toString(); + return; + } + contextData[key] = value; }, extractedProperties); return contextData; diff --git a/integrations/adobe-analytics/package.json b/integrations/adobe-analytics/package.json index 9321ac1c7..3b24c3dc4 100644 --- a/integrations/adobe-analytics/package.json +++ b/integrations/adobe-analytics/package.json @@ -1,7 +1,7 @@ { "name": "@segment/analytics.js-integration-adobe-analytics", "description": "The Adobe Analytics analytics.js integration.", - "version": "1.16.1", + "version": "1.16.2", "keywords": [ "analytics.js", "analytics.js-integration", diff --git a/integrations/adobe-analytics/test/index.test.js b/integrations/adobe-analytics/test/index.test.js index d1c3fdb23..bbb5cf1ac 100644 --- a/integrations/adobe-analytics/test/index.test.js +++ b/integrations/adobe-analytics/test/index.test.js @@ -1175,6 +1175,30 @@ describe('Adobe Analytics', function() { }); analytics.equal(window.s.events, 'prodView,event1,event38'); }); + + it('should stringify bool context data', function() { + adobeAnalytics.options.contextValues = { + 'page.referrer': 'page.referrer', + 'page.url': 'page.title', + 'page.bickenBack': 'page.bickenBack' + }; + analytics.track( + 'Drank Some Milk', + { foo: 'bar' }, + { page: { bickenBack: false } } + ); + analytics.equal( + window.s.contextData['page.referrer'], + window.document.referrer + ); + analytics.equal( + window.s.contextData['page.title'], + window.location.href + ); + analytics.equal(window.s.contextData['page.bickenBack'], 'false'); + analytics.equal(window.s.contextData.foo, 'bar'); + analytics.called(window.s.tl); + }); }); }); @@ -1592,7 +1616,7 @@ describe('Adobe Analytics', function() { ); }); - it('should call trackComplete when a video completes', function() { + it('should set chapterInProgress when a video completes', function() { analytics.track('Video Playback Started', { session_id: sessionId, channel: 'Black Mesa', @@ -1606,7 +1630,7 @@ describe('Adobe Analytics', function() { analytics.stub( adobeAnalytics.mediaHeartbeats[sessionId].heartbeat, - 'trackComplete' + 'trackEvent' ); analytics.track('Video Content Completed', { @@ -1621,7 +1645,12 @@ describe('Adobe Analytics', function() { }); analytics.called( - adobeAnalytics.mediaHeartbeats[sessionId].heartbeat.trackComplete + adobeAnalytics.mediaHeartbeats[sessionId].heartbeat.trackEvent, + window.ADB.va.MediaHeartbeat.Event.ChapterComplete + ); + analytics.equal( + false, + adobeAnalytics.mediaHeartbeats[sessionId].chapterInProgress ); }); @@ -1658,7 +1687,7 @@ describe('Adobe Analytics', function() { ); }); - it('should delete the instance when the session is over', function() { + it('should call final hb methods and delete the instance when the session is over', function() { analytics.track('Video Playback Started', { session_id: sessionId, channel: 'Black Mesa', @@ -1674,6 +1703,7 @@ describe('Adobe Analytics', function() { // We need to save this reference for the upcoming check, since we delete the higher property after the next call. var heartbeatRef = adobeAnalytics.mediaHeartbeats[sessionId].heartbeat; + analytics.stub(heartbeatRef, 'trackComplete'); analytics.stub(heartbeatRef, 'trackSessionEnd'); analytics.track('Video Playback Completed', { @@ -1687,8 +1717,9 @@ describe('Adobe Analytics', function() { livestream: false }); - analytics.assert(!adobeAnalytics.mediaHeartbeats[sessionId]); + analytics.called(heartbeatRef.trackComplete); analytics.called(heartbeatRef.trackSessionEnd); + analytics.assert(!adobeAnalytics.mediaHeartbeats[sessionId]); }); it('should start an Ad Break and Ad Tracking when an ad starts', function() { @@ -1833,6 +1864,26 @@ describe('Adobe Analytics', function() { 'trackPause' ); }); + + it('should return the playhead value from the window object', function() { + analytics.track('Video Playback Started', { + session_id: sessionId, + channel: 'Black Mesa', + video_player: 'Transit Announcement System', + playhead: 5, + asset_id: 'Gordon Freeman', + title: 'Half-Life', + total_length: 1260, + livestream: false + }); + + window._segHBPlayheads[sessionId] = 5.111; + + var actual = adobeAnalytics.mediaHeartbeats[ + sessionId + ].delegate.getCurrentPlaybackTime(); + analytics.assert(actual, 5.111); + }); }); }); });