reducers: Make LINK_CLICK finalize but not close

... the interaction.

Following on from Iccba3c4c, LINK_CLICK shouldn't be considered the end
of an interaction because the link or preview can still be abandoned by
the user. Unlike ABANDON_END and LINK_DWELL, therefore, LINK_CLICK
musn't destroy the interaction but indicate that no more events should
be enqueued for the interaction.

More concretely, if the user has clicked the link or the preview, then
a "dwelledButAbandoned" or "dismissed" event shouldn't be logged.

Changes:
* Distinguish between "finalizing" and "closing" an interaction, where
  the latter is the current behavior of ABANDON_END, LINK_DWELL, and
  LINK_CLICK, in the eventLogging reducer and associated tests.
* If the interaction is finalized, then either the "dwelledButAbandoned"
  or "dismissed" events shouldn't be logged.

Bug: T162924
Change-Id: I09d8776da992053f89a77508e29a7cde3cfeeac6
This commit is contained in:
Sam Smith 2017-04-15 12:18:00 +01:00 committed by jdlrobson
parent 9590284c70
commit 56aeeccb0d
4 changed files with 82 additions and 17 deletions

Binary file not shown.

Binary file not shown.

View file

@ -33,16 +33,26 @@ function getBaseData( bootAction ) {
* Creates an event that, when mixed into the base data (see `getBaseData`), * Creates an event that, when mixed into the base data (see `getBaseData`),
* represents the user abandoning a link or preview. * represents the user abandoning a link or preview.
* *
* Since the event should be logged when the user has either abandoned a link or
* dwelled on a different link, we refer to these events as "closing" events as
* the link interaction has finished and a new one will be created later.
*
* If the link interaction is finalized, i.e. if an event has already been
* logged for the link interaction, then no closing event is created.
*
* @param {Object} interaction * @param {Object} interaction
* @param {Number} endTimestamp * @return {Object|undefined}
* @return {Object}
*/ */
function createAbandonEvent( interaction ) { function createClosingEvent( interaction ) {
var result = { var result = {
linkInteractionToken: interaction.token, linkInteractionToken: interaction.token,
totalInteractionTime: Math.round( interaction.finished - interaction.started ) totalInteractionTime: Math.round( interaction.finished - interaction.started )
}; };
if ( interaction.finalized ) {
return undefined;
}
// Has the preview been shown? If so, then, in the context of the // Has the preview been shown? If so, then, in the context of the
// instrumentation, then the preview has been dismissed by the user // instrumentation, then the preview has been dismissed by the user
// rather than the user has abandoned the link. // rather than the user has abandoned the link.
@ -166,7 +176,7 @@ module.exports = function ( state, action ) {
// Was the user interacting with another link? If so, then log the // Was the user interacting with another link? If so, then log the
// abandoned event. // abandoned event.
event: state.interaction ? createAbandonEvent( state.interaction ) : undefined event: state.interaction ? createClosingEvent( state.interaction ) : undefined
} ); } );
case actionTypes.PREVIEW_DWELL: case actionTypes.PREVIEW_DWELL:
@ -178,7 +188,9 @@ module.exports = function ( state, action ) {
case actionTypes.LINK_CLICK: case actionTypes.LINK_CLICK:
return nextState( state, { return nextState( state, {
interaction: undefined, interaction: {
finalized: true
},
event: { event: {
action: 'opened', action: 'opened',
linkInteractionToken: state.interaction.token, linkInteractionToken: state.interaction.token,
@ -199,7 +211,7 @@ module.exports = function ( state, action ) {
if ( !state.interaction.isUserDwelling ) { if ( !state.interaction.isUserDwelling ) {
return nextState( state, { return nextState( state, {
interaction: undefined, interaction: undefined,
event: createAbandonEvent( state.interaction ) event: createClosingEvent( state.interaction )
} ); } );
} }

View file

@ -221,15 +221,16 @@ QUnit.test( 'LINK_DWELL doesn\'t start a new interaction under certain condition
QUnit.test( QUnit.test(
'LINK_DWELL should enqueue a "dismissed" or "dwelledButAbandoned" event under certain conditions', 'LINK_DWELL should enqueue a "dismissed" or "dwelledButAbandoned" event under certain conditions',
function ( assert ) { function ( assert ) {
var state, var token = '0987654321',
now = Date.now(); now = Date.now(),
state;
// Read: The user dwells on link A, abandons it, and dwells on link B fewer // Read: The user dwells on link A, abandons it, and dwells on link B fewer
// than 300 ms after (before the ABANDON_END action is reduced). // than 300 ms after (before the ABANDON_END action is reduced).
state = eventLogging( undefined, { state = eventLogging( undefined, {
type: 'LINK_DWELL', type: 'LINK_DWELL',
el: this.link, el: this.link,
token: '0987654321', token: token,
timestamp: now timestamp: now
} ); } );
@ -253,6 +254,33 @@ QUnit.test(
action: 'dwelledButAbandoned' action: 'dwelledButAbandoned'
} }
); );
// ---
state = eventLogging( undefined, {
type: 'LINK_DWELL',
el: this.link,
token: token,
timestamp: now
} );
state = eventLogging( state, {
type: 'LINK_CLICK',
el: this.link
} );
state = eventLogging( state, {
type: 'LINK_DWELL',
el: $( '<a>' ),
token: 'banana',
timestamp: now + 500
} );
assert.strictEqual(
state.event,
undefined,
'It shouldn\'t enqueue either event if the interaction is finalized.'
);
} }
); );
@ -286,8 +314,8 @@ QUnit.test( 'LINK_CLICK should enqueue an "opened" event', function ( assert ) {
); );
assert.strictEqual( assert.strictEqual(
state.interaction, state.interaction.finalized,
undefined, true,
'It should finalize the interaction.' 'It should finalize the interaction.'
); );
} ); } );
@ -504,7 +532,7 @@ QUnit.test( 'ABANDON_END should enqueue an event', function ( assert ) {
assert.strictEqual( assert.strictEqual(
state.interaction, state.interaction,
undefined, undefined,
'It should finalize the interaction.' 'It should close the interaction.'
); );
// --- // ---
@ -541,14 +569,16 @@ QUnit.test( 'ABANDON_END should enqueue an event', function ( assert ) {
} ); } );
QUnit.test( 'ABANDON_END doesn\'t enqueue an event under certain conditions', function ( assert ) { QUnit.test( 'ABANDON_END doesn\'t enqueue an event under certain conditions', function ( assert ) {
var dwelledState, var token = '0987654321',
now = Date.now(),
dwelledState,
state; state;
dwelledState = eventLogging( undefined, { dwelledState = eventLogging( undefined, {
type: 'LINK_DWELL', type: 'LINK_DWELL',
el: this.link, el: this.link,
token: '0987654321', token: token,
timestamp: Date.now() timestamp: now
} ); } );
state = eventLogging( dwelledState, { state = eventLogging( dwelledState, {
@ -566,12 +596,35 @@ QUnit.test( 'ABANDON_END doesn\'t enqueue an event under certain conditions', fu
state = eventLogging( dwelledState, { state = eventLogging( dwelledState, {
type: 'ABANDON_END', type: 'ABANDON_END',
token: '0987654321' token: token
} ); } );
assert.strictEqual( assert.strictEqual(
state.event, state.event,
undefined, undefined,
'It shouldn\'t enqueue an event if the use is dwelling on the preview or the link.' 'It shouldn\'t enqueue an event if the user is dwelling on the preview or the link.'
);
// ---
state = eventLogging( dwelledState, {
type: 'LINK_CLICK',
timestamp: now + 500
} );
state = eventLogging( state, {
type: 'ABANDON_START',
timestamp: now + 700
} );
state = eventLogging( state, {
type: 'ABANDON_END',
timestamp: now + 1000 // ABANDON_END_DELAY is 300 ms.
} );
assert.strictEqual(
state.event,
undefined,
'It shouldn\'t enqueue an event if the interaction is finalized.'
); );
} ); } );