Skip to content

Commit 1159d23

Browse files
committed
feat: add GTM integration
1 parent 6aebf3d commit 1159d23

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed

src/initialize.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ import {
6868
import {
6969
configure as configureAnalytics, SegmentAnalyticsService, identifyAnonymousUser, identifyAuthenticatedUser,
7070
} from './analytics';
71-
import { GoogleAnalyticsLoader } from './scripts';
71+
import { GoogleAnalyticsLoader, GoogleTagManagerLoader } from './scripts';
7272
import {
7373
getAuthenticatedHttpClient,
7474
configure as configureAuth,
@@ -269,8 +269,8 @@ function applyOverrideHandlers(overrides) {
269269
* @param {*} [options.analyticsService=SegmentAnalyticsService] The `AnalyticsService`
270270
* implementation to use.
271271
* @param {*} [options.authMiddleware=[]] An array of middleware to apply to http clients in the auth service.
272-
* @param {*} [options.externalScripts=[GoogleAnalyticsLoader]] An array of externalScripts.
273-
* By default added GoogleAnalyticsLoader.
272+
* @param {*} [options.externalScripts=[GoogleAnalyticsLoader, GoogleTagManagerLoader]] An array of externalScripts.
273+
* By default adds GoogleAnalyticsLoader and GoogleTagManagerLoader.
274274
* @param {*} [options.requireAuthenticatedUser=false] If true, turns on automatic login
275275
* redirection for unauthenticated users. Defaults to false, meaning that by default the
276276
* application will allow anonymous/unauthenticated sessions.
@@ -290,7 +290,7 @@ export async function initialize({
290290
analyticsService = SegmentAnalyticsService,
291291
authService = AxiosJwtAuthService,
292292
authMiddleware = [],
293-
externalScripts = [GoogleAnalyticsLoader],
293+
externalScripts = [GoogleAnalyticsLoader, GoogleTagManagerLoader],
294294
requireAuthenticatedUser: requireUser = false,
295295
hydrateAuthenticatedUser: hydrateUser = false,
296296
messages,

src/scripts/GoogleTagManagerLoader.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @implements {GoogleTagManagerLoader}
3+
* @memberof module:GoogleTagManagerLoader
4+
*/
5+
class GoogleTagManagerLoader {
6+
constructor({ config }) {
7+
this.gtmId = config.GOOGLE_TAG_MANAGER_ID;
8+
}
9+
10+
loadScript() {
11+
if (!this.gtmId) {
12+
return;
13+
}
14+
15+
global.google_tag_manager = global.google_tag_manager || [];
16+
const { google_tag_manager: googleTagManager } = global;
17+
18+
// If the snippet was invoked do nothing.
19+
if (googleTagManager.invoked) {
20+
return;
21+
}
22+
23+
// Invoked flag, to make sure the snippet
24+
// is never invoked twice.
25+
googleTagManager.invoked = true;
26+
27+
googleTagManager.load = (id) => {
28+
const gtmScript = document.createElement('script');
29+
gtmScript.type = 'text/javascript';
30+
gtmScript.innerHTML = `
31+
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
32+
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
33+
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
34+
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
35+
})(window,document,'script','dataLayer', '${id}');
36+
`;
37+
38+
// Insert our scripts next to the first script element.
39+
const first = document.getElementsByTagName('script')[0];
40+
first.parentNode.insertBefore(gtmScript, first);
41+
};
42+
43+
// Load gtmAnalytics.
44+
googleTagManager.load(this.gtmId);
45+
}
46+
}
47+
48+
export default GoogleTagManagerLoader;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import GoogleTagManagerLoader from './GoogleTagManagerLoader';
2+
3+
describe('GoogleTagManagerLoader', () => {
4+
const mockGTMId = 'GOOGLE_TAG_MANAGER_ID_test_id';
5+
let insertBeforeMock;
6+
7+
beforeEach(() => {
8+
global.google_tag_manager = undefined;
9+
insertBeforeMock = jest.fn();
10+
11+
document.getElementsByTagName = jest.fn(() => [
12+
{
13+
parentNode: {
14+
insertBefore: insertBeforeMock,
15+
},
16+
},
17+
]);
18+
});
19+
20+
it('should load GTM script', () => {
21+
const loader = new GoogleTagManagerLoader({ config: { GOOGLE_TAG_MANAGER_ID: mockGTMId } });
22+
23+
loader.loadScript();
24+
25+
expect(global.google_tag_manager).toBeDefined();
26+
expect(global.google_tag_manager.invoked).toBe(true);
27+
28+
const firstScript = document.getElementsByTagName()[0];
29+
expect(firstScript.parentNode.insertBefore).toHaveBeenCalled();
30+
});
31+
32+
it('should not load script if account is not defined', () => {
33+
const loader = new GoogleTagManagerLoader({ config: { GOOGLE_TAG_MANAGER_ID: '' } });
34+
35+
loader.loadScript();
36+
37+
expect(global.google_tag_manager).toBeUndefined();
38+
});
39+
40+
it('should not load script if google_tag_manager is already invoked', () => {
41+
global.google_tag_manager = { invoked: true };
42+
const loader = new GoogleTagManagerLoader({ config: { GOOGLE_TAG_MANAGER_ID: mockGTMId } });
43+
44+
loader.loadScript();
45+
46+
expect(global.google_tag_manager.invoked).toBe(true);
47+
expect(document.getElementsByTagName).not.toHaveBeenCalled();
48+
});
49+
});

src/scripts/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/* eslint-disable import/prefer-default-export */
22
export { default as GoogleAnalyticsLoader } from './GoogleAnalyticsLoader';
3+
export { default as GoogleTagManagerLoader } from './GoogleTagManagerLoader';

0 commit comments

Comments
 (0)