diff --git a/client/package.json b/client/package.json index 8b6dff4e94ea00..086adaa7c84aa5 100644 --- a/client/package.json +++ b/client/package.json @@ -96,6 +96,7 @@ "react-dom": "16.14.0", "react-final-form": "6.5.9", "react-ga": "3.3.0", + "react-gtm-module": "^2.0.11", "react-helmet": "6.1.0", "react-hotkeys": "2.0.0", "react-i18next": "11.16.9", diff --git a/client/src/analytics/google-tag-manater/GoogleTagManager.tsx b/client/src/analytics/google-tag-manater/GoogleTagManager.tsx new file mode 100644 index 00000000000000..0027871fd47d72 --- /dev/null +++ b/client/src/analytics/google-tag-manater/GoogleTagManager.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; +import TagManager from 'react-gtm-module'; + +import { + devTagManagerId, + prodTagManagerId +} from '../../../../config/analytics-settings'; + +import envData from '../../../../config/env.json'; + +/* eslint-disable @typescript-eslint/ban-types */ +const GoogleTagManager: FC<{}> = () => { + // if we have an ID + // then tags are supported in this environment, + // so initialize them + const segmentId = + envData.deploymentEnv === 'staging' ? devTagManagerId : prodTagManagerId; + if (segmentId) { + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + /* eslint-disable @typescript-eslint/no-unsafe-call */ + TagManager.initialize({ + gtmId: segmentId + }); + } + + return null; +}; + +export default GoogleTagManager; diff --git a/client/src/analytics/google-tag-manater/index.ts b/client/src/analytics/google-tag-manater/index.ts new file mode 100644 index 00000000000000..aa72e99ec3d0b2 --- /dev/null +++ b/client/src/analytics/google-tag-manater/index.ts @@ -0,0 +1 @@ +export { default as GoogleTagManager } from './GoogleTagManager'; diff --git a/client/src/analytics/segment/Segment.tsx b/client/src/analytics/segment/Segment.tsx new file mode 100644 index 00000000000000..f77313ddb9e436 --- /dev/null +++ b/client/src/analytics/segment/Segment.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; + +import { + devSegmentId, + prodSegmentId +} from '../../../../config/analytics-settings'; +import envData from '../../../../config/env.json'; +import segment from './segment-snippet'; + +interface SegmentModel { + load: (id: string) => void; + page: () => void; +} + +const segmentModel: SegmentModel = segment as unknown as SegmentModel; + +/* eslint-disable @typescript-eslint/ban-types */ +const Segment: FC<{}> = () => { + // if we have a key for this environment, load it + const segmentId = + envData.deploymentEnv === 'staging' ? devSegmentId : prodSegmentId; + if (segmentId) { + segmentModel.load(segmentId); + segmentModel.page(); + } + + return null; +}; + +export default Segment; diff --git a/client/src/analytics/segment/index.ts b/client/src/analytics/segment/index.ts new file mode 100644 index 00000000000000..67a18436e91638 --- /dev/null +++ b/client/src/analytics/segment/index.ts @@ -0,0 +1 @@ +export { default as Segment } from './Segment'; diff --git a/client/src/analytics/segment/segment-snippet.js b/client/src/analytics/segment/segment-snippet.js new file mode 100644 index 00000000000000..831e9ef409094c --- /dev/null +++ b/client/src/analytics/segment/segment-snippet.js @@ -0,0 +1,65 @@ +function SegmentSnippet() { + var analytics = []; + + if (analytics.initialize) { + return; + } + if (analytics.invoked) { + window.console && + console.error && + console.error('Segment snippet included twice.'); + return; + } + analytics.invoked = !0; + analytics.methods = [ + 'trackSubmit', + 'trackClick', + 'trackLink', + 'trackForm', + 'pageview', + 'identify', + 'reset', + 'group', + 'track', + 'ready', + 'alias', + 'debug', + 'page', + 'once', + 'off', + 'on', + 'addSourceMiddleware', + 'addIntegrationMiddleware', + 'setAnonymousId', + 'addDestinationMiddleware' + ]; + analytics.factory = function (t) { + return function () { + var e = Array.prototype.slice.call(arguments); + e.unshift(t); + analytics.push(e); + return analytics; + }; + }; + for (var t = 0; t < analytics.methods.length; t++) { + var e = analytics.methods[t]; + analytics[e] = analytics.factory(e); + } + analytics.load = function (t, e) { + var n = document.createElement('script'); + n.type = 'text/javascript'; + n.async = !0; + n.src = + 'https://cdn.segment.com/analytics.js/v1/' + t + '/analytics.min.js'; + var a = document.getElementsByTagName('script')[0]; + a.parentNode.insertBefore(n, a); + analytics._loadOptions = e; + }; + analytics.SNIPPET_VERSION = '4.1.0'; + // analytics.load("SEGMENT_ANALYTICS_KEY"); - don't load here and let the component decide to load or not + // analytics.page(); - don't call the page, each app should call it when it loads a page by itself + + return { ...analytics }; +} + +export default SegmentSnippet(); diff --git a/client/src/templates/Challenges/classic/desktop-layout.tsx b/client/src/templates/Challenges/classic/desktop-layout.tsx index 4bf573240096c7..0eefa0a084d1a4 100644 --- a/client/src/templates/Challenges/classic/desktop-layout.tsx +++ b/client/src/templates/Challenges/classic/desktop-layout.tsx @@ -3,6 +3,8 @@ import React, { useState, ReactElement } from 'react'; import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex'; import { sortChallengeFiles } from '../../../../../utils/sort-challengefiles'; import { challengeTypes } from '../../../../utils/challenge-types'; +import { GoogleTagManager } from '../../../analytics/google-tag-manater'; +import { Segment } from '../../../analytics/segment'; import { ChallengeFile, ChallengeFiles, @@ -110,86 +112,91 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => { } = layoutState; return ( -