Skip to content

Commit

Permalink
Merge pull request #4142 from google/feature/3972-ga-tracking-loading
Browse files Browse the repository at this point in the history
Feature/3972 ga tracking loading
  • Loading branch information
eugene-manuilov authored Oct 12, 2021
2 parents 4847c87 + a905276 commit 5f12f3b
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 93 deletions.
4 changes: 2 additions & 2 deletions assets/js/util/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ describe( 'trackAPIError', () => {
code: 'test-error-code',
},
} );
expect( dataLayerPushSpy ).toHaveBeenCalledTimes( 1 );
expect( dataLayerPushSpy ).toHaveBeenCalledTimes( 3 );
const [
event,
eventName,
eventData,
] = dataLayerPushSpy.mock.calls[ 0 ][ 0 ];
] = dataLayerPushSpy.mock.calls[ 2 ][ 0 ];
expect( event ).toEqual( 'event' );
expect( eventName ).toEqual(
'test-method:test-type/test-identifier/data/test-datapoint'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@ import createDataLayerPush from './createDataLayerPush';
import { SCRIPT_IDENTIFIER, DATA_LAYER } from './constants';

/**
* Returns a function which, when invoked enables tracking and injects the gtag script if necessary.
* Returns a function which, when invoked injects the gtag script if necessary.
*
* @since 1.3.0
* @since n.e.x.t
*
* @param {Object} config Tracking configuration.
* @param {Object} dataLayerTarget Data layer parent object.
* @return {Function} Function that tracks an event.
* @return {Function} Function that injects gtag script if it isn't yet present.
*/
export default function createEnableTracking( config, dataLayerTarget ) {
export default function createInitializeSnippet( config, dataLayerTarget ) {
const dataLayerPush = createDataLayerPush( dataLayerTarget );

let hasInsertedTag;

/**
* Enables tracking by injecting the necessary script tag if not present.
* Injects the necessary script tag if not present.
*/
return function enableTracking() {
config.trackingEnabled = true;

return function initializeSnippet() {
const { document } = global;

if ( document.querySelector( `script[${ SCRIPT_IDENTIFIER }]` ) ) {
if ( undefined === hasInsertedTag ) {
hasInsertedTag = !! document.querySelector(
`script[${ SCRIPT_IDENTIFIER }]`
);
}
if ( hasInsertedTag ) {
return;
}

Expand All @@ -36,6 +40,9 @@ export default function createEnableTracking( config, dataLayerTarget ) {
document.head.appendChild( scriptTag );

dataLayerPush( 'js', new Date() );
dataLayerPush( 'config', config.trackingID );
dataLayerPush( 'config', config.trackingID, {
send_page_view: config.isSiteKitScreen,
} );
hasInsertedTag = true;
};
}
48 changes: 29 additions & 19 deletions assets/js/util/tracking/createTrackEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import { enabledFeatures } from '../../features/index';
*
* @since 1.3.0
*
* @param {Object} config Tracking configuration.
* @param {Object} dataLayerTarget Data layer parent object.
* @param {Object} _global The global window object.
* @param {Object} config Tracking configuration.
* @param {Object} dataLayerTarget Data layer parent object.
* @param {Function} initializeSnippet Function to initialize tracking.
* @param {Object} _global The global window object.
* @return {Function} Function that tracks an event.
*/
export default function createTrackEvent( config, dataLayerTarget, _global ) {
export default function createTrackEvent(
config,
dataLayerTarget,
initializeSnippet,
_global
) {
const dataLayerPush = createDataLayerPush( dataLayerTarget );

/**
Expand All @@ -37,15 +43,13 @@ export default function createTrackEvent( config, dataLayerTarget, _global ) {
userIDHash,
} = config;

if ( _global._gaUserPrefs?.ioo?.() ) {
return;
}

if ( ! trackingEnabled ) {
// Resolve immediately if tracking is disabled.
return;
}

initializeSnippet();

const eventData = {
send_to: trackingID,
event_category: category,
Expand All @@ -61,23 +65,29 @@ export default function createTrackEvent( config, dataLayerTarget, _global ) {
return new Promise( ( resolve ) => {
// This timeout ensures a tracking event does not block the user
// event if it is not sent (in time).
// If this fails, it shouldn't reject the promise since event
// If the event beacon fails, it shouldn't reject the promise since event
// tracking should not result in user-facing errors. It will just
// trigger a console warning.
const failTimeout = setTimeout( () => {
global.console.warn(
const failCallback = () => {
_global.console.warn(
`Tracking event "${ action }" (category "${ category }") took too long to fire.`
);
resolve();
}, 1000 );
};
const failTimeout = setTimeout( failCallback, 1000 );
// eslint-disable-next-line camelcase
const event_callback = () => {
clearTimeout( failTimeout );
resolve();
};

dataLayerPush( 'event', action, { ...eventData, event_callback } );

dataLayerPush( 'event', action, {
...eventData,
event_callback: () => {
clearTimeout( failTimeout );
resolve();
},
} );
// If the client-side opt-out is present, the event_callback will never be called
// so we call it here to prevent the warning and added delay.
if ( _global._gaUserPrefs?.ioo?.() ) {
event_callback();
}
} );
};
}
18 changes: 15 additions & 3 deletions assets/js/util/tracking/createTracking.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Internal dependencies
*/
import createEnableTracking from './createEnableTracking';
import createInitializeSnippet from './createInitializeSnippet';
import createTrackEvent from './createTrackEvent';

const DEFAULT_CONFIG = {
Expand Down Expand Up @@ -37,15 +37,27 @@ export default function createTracking(
.toString()
.replace( /\/+$/, '' );
}
const initializeSnippet = createInitializeSnippet(
config,
dataLayerTarget
);

return {
enableTracking: createEnableTracking( config, dataLayerTarget ),
enableTracking: function enableTracking() {
config.trackingEnabled = true;
},
disableTracking: function disableTracking() {
config.trackingEnabled = false;
},
initializeSnippet,
isTrackingEnabled: function isTrackingEnabled() {
return !! config.trackingEnabled;
},
trackEvent: createTrackEvent( config, dataLayerTarget, _global ),
trackEvent: createTrackEvent(
config,
dataLayerTarget,
initializeSnippet,
_global
),
};
}
8 changes: 5 additions & 3 deletions assets/js/util/tracking/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import createTracking from './createTracking';

const {
isFirstAdmin,
trackingAllowed,
isSiteKitScreen,
trackingEnabled,
trackingID,
referenceSiteURL,
Expand All @@ -36,12 +36,14 @@ const initialConfig = {
trackingID,
referenceSiteURL,
userIDHash,
isSiteKitScreen,
};

const {
enableTracking,
disableTracking,
isTrackingEnabled,
initializeSnippet,
trackEvent,
} = createTracking( initialConfig );

Expand All @@ -61,8 +63,8 @@ function toggleTracking( activeStatus ) {
}

// Bootstrap on import if tracking is allowed.
if ( true === trackingAllowed ) {
toggleTracking( isTrackingEnabled() );
if ( isSiteKitScreen && trackingEnabled ) {
initializeSnippet();
}

export {
Expand Down
29 changes: 0 additions & 29 deletions assets/js/util/tracking/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,33 +201,4 @@ describe( 'trackEvent', () => {
);
consoleWarnSpy.mockClear();
} );

it( 'not push to dataLayer with the opt-out set', async () => {
const push = jest.fn();
const dataLayer = {
[ DATA_LAYER ]: { push },
};

const mockGlobal = {
_gaUserPrefs: {
ioo: () => true,
},
};
const iooSpy = jest.spyOn( mockGlobal._gaUserPrefs, 'ioo' );
const { trackEvent } = createTracking(
{ trackingEnabled: true },
dataLayer,
mockGlobal
);
await fakeTimeouts( () =>
trackEvent(
'test-category',
'test-name',
'test-label',
'test-value'
)
);
expect( iooSpy ).toHaveBeenCalled();
expect( push ).not.toHaveBeenCalled();
} );
} );
2 changes: 1 addition & 1 deletion includes/Core/Util/Tracking.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public function is_active() {
*/
private function inline_js_base_data( $data ) {
global $hook_suffix;
$data['trackingAllowed'] = $this->screens->get_screen( $hook_suffix ) instanceof Screen;
$data['isSiteKitScreen'] = $this->screens->get_screen( $hook_suffix ) instanceof Screen;
$data['trackingEnabled'] = $this->is_active();
$data['trackingID'] = self::TRACKING_ID;

Expand Down
25 changes: 0 additions & 25 deletions includes/Modules/Idea_Hub.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,31 +235,6 @@ public function register() {
*/
add_filter( 'post_class', $this->get_method_proxy( 'update_post_classes' ), 10, 3 );

add_filter(
'googlesitekit_inline_base_data',
function( $data ) {
if (
// Do nothing if tracking is disabled or if it is enabled and already allowed.
empty( $data['trackingEnabled'] ) ||
! empty( $data['trackingAllowed'] ) ||
// Also do nothing if the get_current_screen function is not available.
! function_exists( 'get_current_screen' )
) {
return $data;
}

$screen = get_current_screen();
if ( ! is_null( $screen ) ) {
$data['trackingAllowed'] =
( 'post' === $screen->post_type && 'edit-post' === $screen->id ) ||
'dashboard' === $screen->id;
}

return $data;
},
100
);

add_action(
'admin_footer-edit.php',
function() {
Expand Down

0 comments on commit 5f12f3b

Please sign in to comment.