Skip to content
This repository was archived by the owner on Dec 6, 2024. It is now read-only.

Commit 5867817

Browse files
Add support in the frontend for a Google Analytics tracking ID (#860)
1 parent d3e93fa commit 5867817

File tree

8 files changed

+298
-3
lines changed

8 files changed

+298
-3
lines changed

frontend/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import TopicareaSettings from "./containers/TopicareaSettings";
3535
import PublishingGuidanceSettings from "./containers/PublishingGuidanceSettings";
3636
import PublishedSiteSettings from "./containers/PublishedSiteSettings";
3737
import EditPublishingGuidance from "./containers/EditPublishingGuidance";
38+
import EditAnalytics from "./containers/EditAnalytics";
3839
import EditHomepageContent from "./containers/EditHomepageContent";
3940
import CreateTopicArea from "./containers/CreateTopicArea";
4041
import EditTopicArea from "./containers/EditTopicArea";
@@ -126,6 +127,11 @@ function App() {
126127
title: t("PageTitle.EditHomepageContent"),
127128
component: EditHomepageContent,
128129
},
130+
{
131+
path: "/admin/settings/publishedsite/analyticsedit",
132+
title: t("PageTitle.EditAnalytics"),
133+
component: EditAnalytics,
134+
},
129135
{
130136
path: "/admin/settings/publishedsite/navbaredit",
131137
title: t("PageTitle.EditNavbar"),
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import React from "react";
2+
import { useHistory } from "react-router-dom";
3+
import { useForm } from "react-hook-form";
4+
import { useSettings } from "../hooks";
5+
import BackendService from "../services/BackendService";
6+
import Button from "../components/Button";
7+
import Breadcrumbs from "../components/Breadcrumbs";
8+
import Spinner from "../components/Spinner";
9+
import TextField from "../components/TextField";
10+
import { useTranslation } from "react-i18next";
11+
12+
interface FormValues {
13+
trackingID: string;
14+
}
15+
16+
function EditAnalytics() {
17+
const history = useHistory();
18+
const { settings, reloadSettings, loadingSettings } = useSettings(true);
19+
const { register, handleSubmit, errors } = useForm<FormValues>();
20+
const { t } = useTranslation();
21+
22+
if (settings.analyticsTrackingId == "NA") {
23+
settings.analyticsTrackingId = "";
24+
}
25+
26+
const onSubmit = async (values: FormValues) => {
27+
const trackingID = values.trackingID;
28+
29+
await BackendService.updateSetting(
30+
"analyticsTrackingId",
31+
trackingID,
32+
settings.updatedAt ? settings.updatedAt : new Date()
33+
);
34+
35+
await reloadSettings();
36+
history.push("/admin/settings/publishedsite", {
37+
alert: {
38+
type: "success",
39+
message: t("SettingsAnalyticsEditSuccess"),
40+
},
41+
});
42+
};
43+
44+
const onClearAndSave = async () => {
45+
await BackendService.updateSetting(
46+
"analyticsTrackingId",
47+
"NA",
48+
settings.updatedAt ? settings.updatedAt : new Date()
49+
);
50+
51+
await reloadSettings();
52+
history.push("/admin/settings/publishedsite", {
53+
alert: {
54+
type: "success",
55+
message: t("SettingsAnalyticsEditSuccess"),
56+
},
57+
});
58+
};
59+
60+
const onCancel = () => {
61+
history.push("/admin/settings/publishedsite");
62+
};
63+
64+
const crumbs = [
65+
{
66+
label: t("Settings"),
67+
url: "/admin/settings",
68+
},
69+
{
70+
label: t("SettingsPublishedSite"),
71+
url: "/admin/settings/publishedsite",
72+
},
73+
{
74+
label: t("SettingsAnalyticsEdit"),
75+
},
76+
];
77+
78+
return (
79+
<div className="grid-row">
80+
<div className="grid-col-8">
81+
<Breadcrumbs crumbs={crumbs} />
82+
<h1 id="settingsAnalyticsLabel">{t("SettingsAnalyticsEdit")}</h1>
83+
84+
<p>{t("SettingsAnalyticsDescription")}</p>
85+
86+
{loadingSettings ? (
87+
<Spinner
88+
className="text-center margin-top-9"
89+
label={t("LoadingSpinnerLabel")}
90+
/>
91+
) : (
92+
<>
93+
<form
94+
onSubmit={handleSubmit(onSubmit)}
95+
className="edit-homepage-content-form usa-form usa-form--large"
96+
data-testid="EditAnalyticsForm"
97+
aria-labelledby="settingsAnalyticsLabel"
98+
>
99+
<TextField
100+
id="trackingID"
101+
name="trackingID"
102+
label={t("SettingsAnalyticsHint")}
103+
defaultValue={settings.analyticsTrackingId}
104+
register={register}
105+
validate={(input: string) => {
106+
return !input || input.startsWith("UA");
107+
}}
108+
error={errors.trackingID && t("SettingsAnalyticsEditError")}
109+
required
110+
/>
111+
112+
<br />
113+
114+
<Button type="submit" disabled={loadingSettings}>
115+
{t("Save")}
116+
</Button>
117+
118+
<Button
119+
type="button"
120+
disabled={loadingSettings}
121+
onClick={onClearAndSave}
122+
>
123+
{t("ClearAndSave")}
124+
</Button>
125+
126+
<Button
127+
variant="unstyled"
128+
type="button"
129+
className="margin-left-1 text-base-dark hover:text-base-darker active:text-base-darkest"
130+
onClick={onCancel}
131+
>
132+
{t("Cancel")}
133+
</Button>
134+
</form>
135+
</>
136+
)}
137+
</div>
138+
</div>
139+
);
140+
}
141+
142+
export default EditAnalytics;

frontend/src/containers/PublishedSiteSettings.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import Spinner from "../components/Spinner";
66
import Button from "../components/Button";
77
import MarkdownRender from "../components/MarkdownRender";
88
import Link from "../components/Link";
9-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
10-
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
119
import { useTranslation } from "react-i18next";
1210
import "./PublishedSiteSettings.css";
1311

@@ -25,6 +23,10 @@ function PublishedSiteSettings() {
2523
history.push("/admin/settings/publishedsite/navbaredit");
2624
};
2725

26+
const onAnalyticsEdit = () => {
27+
history.push("/admin/settings/publishedsite/analyticsedit");
28+
};
29+
2830
return (
2931
<SettingsLayout>
3032
<h1>{t("PublishedSiteSettings.PublishedSite")}</h1>
@@ -188,6 +190,41 @@ function PublishedSiteSettings() {
188190
<div className="grid-col flex-3 text-right"></div>
189191
</div>
190192
)}
193+
194+
<hr
195+
style={{
196+
border: "none",
197+
height: "1px",
198+
backgroundColor: "#dfe1e2",
199+
margin: "2rem 0",
200+
}}
201+
/>
202+
203+
<h2 className="margin-top-2-important">
204+
{t("PublishedSiteSettings.Analytics")}
205+
</h2>
206+
207+
{loading ? (
208+
<Spinner
209+
className="margin-top-3 text-center"
210+
label={t("LoadingSpinnerLabel")}
211+
/>
212+
) : (
213+
<div className="grid-row margin-top-3-important">
214+
<div className="grid-col flex-9 text-left">
215+
{t("PublishedSiteSettings.AnalyticsDescription")}
216+
</div>
217+
<div className="grid-col flex-3 text-right">
218+
<Button
219+
className="margin-top-0"
220+
variant="outline"
221+
onClick={onAnalyticsEdit}
222+
>
223+
{t("Edit")}
224+
</Button>
225+
</div>
226+
</div>
227+
)}
191228
</SettingsLayout>
192229
);
193230
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from "react";
2+
import { render, fireEvent, act, screen } from "@testing-library/react";
3+
import userEvent from "@testing-library/user-event";
4+
import { createMemoryHistory } from "history";
5+
import { Route, Router } from "react-router-dom";
6+
import BackendService from "../../services/BackendService";
7+
import EditAnalytics from "../EditAnalytics";
8+
9+
jest.mock("../../hooks");
10+
jest.mock("../../services/BackendService");
11+
12+
const history = createMemoryHistory();
13+
history.push("/admin/settings/publishedsite/analyticsedit");
14+
jest.spyOn(history, "push");
15+
16+
beforeEach(async () => {
17+
await act(async () => {
18+
render(
19+
<Router history={history}>
20+
<Route path="/admin/settings/publishedsite/analyticsedit">
21+
<EditAnalytics />
22+
</Route>
23+
</Router>
24+
);
25+
});
26+
});
27+
28+
test("submits the analytics tracking id", async () => {
29+
userEvent.clear(
30+
screen.getByLabelText("Google Universal Analytics tracking ID*")
31+
);
32+
userEvent.type(
33+
screen.getByLabelText("Google Universal Analytics tracking ID*"),
34+
"UA12345"
35+
);
36+
37+
await act(async () => {
38+
fireEvent.submit(screen.getByTestId("EditAnalyticsForm"));
39+
});
40+
41+
expect(BackendService.updateSetting).toBeCalledTimes(1);
42+
expect(BackendService.updateSetting).toHaveBeenCalledWith(
43+
"analyticsTrackingId",
44+
"UA12345",
45+
expect.anything()
46+
);
47+
});
48+
49+
test("clears and saves the analytics tracking id", async () => {
50+
await act(async () => {
51+
fireEvent.click(screen.getByRole("button", { name: "Clear and Save" }));
52+
});
53+
expect(BackendService.updateSetting).toBeCalledTimes(1);
54+
expect(BackendService.updateSetting).toHaveBeenCalledWith(
55+
"analyticsTrackingId",
56+
"NA",
57+
expect.anything()
58+
);
59+
});
60+
61+
test("invokes the cancel function when user clicks cancel", async () => {
62+
await act(async () => {
63+
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
64+
});
65+
expect(history.push).toHaveBeenCalledWith("/admin/settings/publishedsite");
66+
});

frontend/src/containers/__tests__/PublishedSiteSettings.test.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ test("renders the headline statement", async () => {
6060
expect(getByText("Kingdom of Wakanda")).toBeInTheDocument();
6161
});
6262

63+
test("renders the analytics section", async () => {
64+
const { getByText } = render(<PublishedSiteSettings />, {
65+
wrapper: MemoryRouter,
66+
});
67+
expect(getByText("Web analytics")).toBeInTheDocument();
68+
});
69+
6370
test("renders a button to edit", async () => {
6471
const { getAllByRole } = render(<PublishedSiteSettings />, {
6572
wrapper: MemoryRouter,

frontend/src/containers/__tests__/__snapshots__/PublishedSiteSettings.test.tsx.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,32 @@ exports[`published site settings should match snapshot 1`] = `
374374
class="grid-col flex-3 text-right"
375375
/>
376376
</div>
377+
<hr
378+
style="height: 1px; background-color: rgb(223, 225, 226); margin: 2rem 0px;"
379+
/>
380+
<h2
381+
class="margin-top-2-important"
382+
>
383+
Web analytics
384+
</h2>
385+
<div
386+
class="grid-row margin-top-3-important"
387+
>
388+
<div
389+
class="grid-col flex-9 text-left"
390+
>
391+
Add your web analytics tracking ID to get insights into how your visitors are interacting with Performance Dashboard.
392+
</div>
393+
<div
394+
class="grid-col flex-3 text-right"
395+
>
396+
<button
397+
class="usa-button usa-button--outline margin-top-0"
398+
>
399+
Edit
400+
</button>
401+
</div>
402+
</div>
377403
</main>
378404
</div>
379405
</div>

frontend/src/locales/en/translation.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"EditPublishingGuidance": "Edit Publishing Guidance",
1212
"PublishedSiteSettings": "Published Site Settings",
1313
"EditHomepageContent": "Edit Homepage Content",
14+
"EditAnalytics": "Analytics",
1415
"EditNavbar": "Edit Navigation Bar",
1516
"CreateTopicArea": "Create {{topicArea}}",
1617
"EditTopicArea": "Edit {{topicArea}}",
@@ -109,6 +110,7 @@
109110
"Delete": "Delete",
110111
"Save": "Save",
111112
"Edit": "Edit",
113+
"ClearAndSave": "Clear and Save",
112114
"CopyLink": "Copy link: {{title}}",
113115
"LinkCopied": "Link copied: {{title}}",
114116
"EditHeader": "Edit header",
@@ -627,6 +629,11 @@
627629
"SettingsTopicAreaNameEditError": "Please specify a name",
628630
"SettingsTopicAreaNameEditProblem": "There was a problem editing topic area {{name}}. Please retry.",
629631
"SettingsTopicAreaNameEditSuccess": "Topic area {{name}} was successfully edited.",
632+
"SettingsAnalyticsEdit": "Web analytics",
633+
"SettingsAnalyticsDescription": "Add an existing Google Analytics tracking ID to your Performance Dashboard and view the usage data in your Google Analytics account. Performance Dashboard currently only supports Google Analytics tracking.",
634+
"SettingsAnalyticsHint": "Google Universal Analytics tracking ID",
635+
"SettingsAnalyticsEditSuccess": "Analytics tracking ID successfully edited.",
636+
"SettingsAnalyticsEditError": "Invalid Google Universal Analytics tracking ID",
630637
"TopicArea": "Topic area",
631638
"TopicArea_plural": "Topic areas",
632639
"TopicAreas": "Topic areas",
@@ -679,7 +686,9 @@
679686
"HomepageContentHeader": "Homepage content",
680687
"HomepageContentDescription": "These components appear on the homepage of your published site and explain what your published site is about.",
681688
"Headline": "Headline",
682-
"Edit": "Edit"
689+
"Edit": "Edit",
690+
"Analytics": "Web analytics",
691+
"AnalyticsDescription": "Add your web analytics tracking ID to get insights into how your visitors are interacting with Performance Dashboard."
683692
},
684693
"AdminFooter": {
685694
"HelpMessage": "Having technical issues with the system?",

frontend/src/models/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ export type PublicSettings = {
255255
customFaviconS3Key?: string;
256256
contactEmailAddress?: string;
257257
contactUsContent?: string;
258+
analyticsTrackingId?: string;
258259
};
259260

260261
export type Settings = {
@@ -279,6 +280,7 @@ export type Settings = {
279280
contactEmailAddress?: string;
280281
adminContactEmailAddress?: string;
281282
contactUsContent?: string;
283+
analyticsTrackingId?: string;
282284
};
283285

284286
export type Metric = {

0 commit comments

Comments
 (0)