From 8b406709d31791ae17d035b8085f43d1e7cc0781 Mon Sep 17 00:00:00 2001 From: Brooke Date: Thu, 25 Aug 2022 12:48:54 -0700 Subject: [PATCH 1/2] TCA-314 #comment This PR adds Segment analytics with env-specific behavior. It also removes the FCC GA tracking. #time 6h --- client/src/analytics/segment/Segment.tsx | 29 ++++ client/src/analytics/segment/index.ts | 1 + .../src/analytics/segment/segment-snippet.js | 65 ++++++++ .../Challenges/classic/desktop-layout.tsx | 149 +++++++++--------- .../Challenges/classic/tc-layout.tsx | 104 ++++++------ config/analytics-settings.js | 8 +- 6 files changed, 233 insertions(+), 123 deletions(-) create mode 100644 client/src/analytics/segment/Segment.tsx create mode 100644 client/src/analytics/segment/index.ts create mode 100644 client/src/analytics/segment/segment-snippet.js diff --git a/client/src/analytics/segment/Segment.tsx b/client/src/analytics/segment/Segment.tsx new file mode 100644 index 00000000000000..28f76e0a57635f --- /dev/null +++ b/client/src/analytics/segment/Segment.tsx @@ -0,0 +1,29 @@ +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; + +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..fe5c2cc8d92cbc 100644 --- a/client/src/templates/Challenges/classic/desktop-layout.tsx +++ b/client/src/templates/Challenges/classic/desktop-layout.tsx @@ -3,6 +3,7 @@ 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 { Segment } from '../../../analytics/segment'; import { ChallengeFile, ChallengeFiles, @@ -110,86 +111,90 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => { } = layoutState; return ( -
- -
- - {!projectBasedChallenge && showInstructions && ( - - {instructions} - - )} - {!projectBasedChallenge && displayEditor && ( - - )} + <> +
+ +
+ + {!projectBasedChallenge && showInstructions && ( + + {instructions} + + )} + {!projectBasedChallenge && displayEditor && ( + + )} - {challengeFile && displayEditor && ( - - - + - {editor} - - {displayNotes && ( - - )} - {displayNotes && ( - - {notes} - - )} - - - )} - - {(displayPreview || displayConsole) && ( - - )} - - {(displayPreview || displayConsole) && ( - - - {displayPreview && ( - - {preview} - - )} - {displayConsole && displayPreview && ( - - )} - {displayConsole && ( - {testOutput} + {editor} - )} - - - )} - + {displayNotes && ( + + )} + {displayNotes && ( + + {notes} + + )} + + + )} + + {(displayPreview || displayConsole) && ( + + )} + + {(displayPreview || displayConsole) && ( + + + {displayPreview && ( + + {preview} + + )} + {displayConsole && displayPreview && ( + + )} + {displayConsole && ( + + {testOutput} + + )} + + + )} + +
-
+ + + ); }; diff --git a/client/src/templates/Challenges/classic/tc-layout.tsx b/client/src/templates/Challenges/classic/tc-layout.tsx index 66776ef5a163c4..fd9ae1d5c8bd8e 100755 --- a/client/src/templates/Challenges/classic/tc-layout.tsx +++ b/client/src/templates/Challenges/classic/tc-layout.tsx @@ -1,7 +1,9 @@ import { first } from 'lodash-es'; import React, { ReactElement } from 'react'; import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex'; + import { sortChallengeFiles } from '../../../../../utils/sort-challengefiles'; +import Segment from '../../../analytics/segment/Segment'; import { ChallengeFile, ChallengeFiles, @@ -71,62 +73,66 @@ const TcLayout = (props: TcLayoutProps): JSX.Element => { } = layoutState; return ( -
- - - {instructions} - - - - - - - {challengeFile && ( - - +
+ + + {instructions} + + + + + + + {challengeFile && ( + - {editor} + + {editor} + + + )} + + + {displayNotes && ( + + )} + {displayNotes && ( + + {notes} + + )} + + {hasPreview && ( + <> + + + {preview} - + )} - - {displayNotes && ( - )} - {displayNotes && ( - - {notes} + + {testOutput} - )} + + + +
- {hasPreview && ( - <> - - - {preview} - - - )} - - - - {testOutput} - -
-
-
-
+ + ); }; diff --git a/config/analytics-settings.js b/config/analytics-settings.js index 106725cc3b67eb..1a0d36f8f7a854 100644 --- a/config/analytics-settings.js +++ b/config/analytics-settings.js @@ -1,2 +1,6 @@ -exports.prodAnalyticsId = 'UA-55446531-10'; -exports.devAnalyticsId = 'UA-55446531-19'; +exports.prodAnalyticsId = null; +exports.prodSegmentId = '8fCbi94o3ruUUGxRRGxWu194t6iVq9LH'; + +exports.devAnalyticsId = null; +// TODO: TCA-371 set this to null so we're not tracking non-prod +exports.devSegmentId = '8fCbi94o3ruUUGxRRGxWu194t6iVq9LH'; From 624a624b195a96e69243ccb7eba7dfe01286dd23 Mon Sep 17 00:00:00 2001 From: Brooke Date: Thu, 25 Aug 2022 13:03:56 -0700 Subject: [PATCH 2/2] TCA-314 #comment This commit adds GTM to FCC w/env-specific accounts #time 15m --- client/package.json | 1 + .../google-tag-manater/GoogleTagManager.tsx | 29 +++++++++++++++++++ .../src/analytics/google-tag-manater/index.ts | 1 + client/src/analytics/segment/Segment.tsx | 3 +- .../Challenges/classic/desktop-layout.tsx | 2 ++ .../Challenges/classic/tc-layout.tsx | 2 ++ config/analytics-settings.js | 7 +++-- package-lock.json | 12 ++++++++ 8 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 client/src/analytics/google-tag-manater/GoogleTagManager.tsx create mode 100644 client/src/analytics/google-tag-manater/index.ts 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 index 28f76e0a57635f..f77313ddb9e436 100644 --- a/client/src/analytics/segment/Segment.tsx +++ b/client/src/analytics/segment/Segment.tsx @@ -14,7 +14,8 @@ interface SegmentModel { const segmentModel: SegmentModel = segment as unknown as SegmentModel; -const Segment: FC = () => { +/* 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; diff --git a/client/src/templates/Challenges/classic/desktop-layout.tsx b/client/src/templates/Challenges/classic/desktop-layout.tsx index fe5c2cc8d92cbc..0eefa0a084d1a4 100644 --- a/client/src/templates/Challenges/classic/desktop-layout.tsx +++ b/client/src/templates/Challenges/classic/desktop-layout.tsx @@ -3,6 +3,7 @@ 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, @@ -194,6 +195,7 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
+ ); }; diff --git a/client/src/templates/Challenges/classic/tc-layout.tsx b/client/src/templates/Challenges/classic/tc-layout.tsx index fd9ae1d5c8bd8e..edf9a605054df0 100755 --- a/client/src/templates/Challenges/classic/tc-layout.tsx +++ b/client/src/templates/Challenges/classic/tc-layout.tsx @@ -3,6 +3,7 @@ import React, { ReactElement } from 'react'; import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex'; import { sortChallengeFiles } from '../../../../../utils/sort-challengefiles'; +import { GoogleTagManager } from '../../../analytics/google-tag-manater'; import Segment from '../../../analytics/segment/Segment'; import { ChallengeFile, @@ -132,6 +133,7 @@ const TcLayout = (props: TcLayoutProps): JSX.Element => { + ); }; diff --git a/config/analytics-settings.js b/config/analytics-settings.js index 1a0d36f8f7a854..976e96c16d6ae5 100644 --- a/config/analytics-settings.js +++ b/config/analytics-settings.js @@ -1,6 +1,9 @@ exports.prodAnalyticsId = null; -exports.prodSegmentId = '8fCbi94o3ruUUGxRRGxWu194t6iVq9LH'; - exports.devAnalyticsId = null; + +exports.prodSegmentId = '8fCbi94o3ruUUGxRRGxWu194t6iVq9LH'; // TODO: TCA-371 set this to null so we're not tracking non-prod exports.devSegmentId = '8fCbi94o3ruUUGxRRGxWu194t6iVq9LH'; + +exports.prodTagManagerId = 'GTM-MXXQHG8'; +exports.devTagManagerId = 'GTM-W7B537Z'; diff --git a/package-lock.json b/package-lock.json index 6f01f465cef24d..7c10e249b2838f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -977,6 +977,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", @@ -43540,6 +43541,11 @@ "react": "^15.6.2 || ^16.0 || ^17" } }, + "node_modules/react-gtm-module": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz", + "integrity": "sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw==" + }, "node_modules/react-helmet": { "version": "6.1.0", "license": "MIT", @@ -57834,6 +57840,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", @@ -84527,6 +84534,11 @@ "version": "3.3.0", "requires": {} }, + "react-gtm-module": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz", + "integrity": "sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw==" + }, "react-helmet": { "version": "6.1.0", "requires": {