Skip to content

Commit

Permalink
Merge a565465 into c1d6fce
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian committed Mar 18, 2021
2 parents c1d6fce + a565465 commit 14d2225
Show file tree
Hide file tree
Showing 14 changed files with 504 additions and 6 deletions.
2 changes: 2 additions & 0 deletions spotlight-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"wait-for-localhost": "^3.3.0"
},
"devDependencies": {
"@segment/analytics.js-core": "^4.1.5",
"@testing-library/jest-dom": "^5.11.1",
"@testing-library/react": "^11.1.1",
"@testing-library/user-event": "^12.7.0",
Expand All @@ -105,6 +106,7 @@
"jest-date-mock": "^1.0.8",
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-fetch-mock": "^3.0.3",
"jest-mock-extended": "^1.0.13",
"lint-staged": ">=10"
},
"browserslist": {
Expand Down
7 changes: 7 additions & 0 deletions spotlight-client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Spotlight by Recidiviz</title>
<!-- Segment tracking snippet -->
<!-- prettier-ignore -->
<script>
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{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(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics._writeKey="DMgmUv5wXE2lEEtRaXRpzxCA8dK7BHBE";analytics.SNIPPET_VERSION="4.13.2";
analytics.load("DMgmUv5wXE2lEEtRaXRpzxCA8dK7BHBE");
}}();
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
31 changes: 30 additions & 1 deletion spotlight-client/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
fireEvent,
} from "@testing-library/react";
import testContent from "./contentApi/sources/us_nd";
import { renderNavigableApp } from "./testUtils";
import { renderNavigableApp, segmentMock } from "./testUtils";

describe("navigation", () => {
/**
Expand Down Expand Up @@ -136,4 +136,33 @@ describe("navigation", () => {
await screen.findByRole("heading", { name: /North Dakota/, level: 1 })
).toBeInTheDocument();
});

test("pageview tracking", async () => {
segmentMock.page.mockReset();

const {
history: { navigate },
} = renderNavigableApp({ route: "/us-nd" });

expect(document.title).toBe("North Dakota — Spotlight by Recidiviz");
expect(segmentMock.page).toHaveBeenCalledTimes(1);

await act(() => navigate("/us-nd/collections/prison"));

expect(document.title).toBe(
"Prison — North Dakota — Spotlight by Recidiviz"
);
expect(segmentMock.page).toHaveBeenCalledTimes(2);

// in-page navigation doesn't trigger additional pageviews
await act(() => navigate("/us-nd/collections/prison/2"));
expect(segmentMock.page).toHaveBeenCalledTimes(2);

await act(() => navigate("/us-nd/collections/sentencing"));

expect(document.title).toBe(
"Sentencing — North Dakota — Spotlight by Recidiviz"
);
expect(segmentMock.page).toHaveBeenCalledTimes(3);
});
});
2 changes: 2 additions & 0 deletions spotlight-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import GlobalStyles from "./GlobalStyles";
import PageNarrative from "./PageNarrative";
import PageNotFound from "./PageNotFound";
import PageTenant from "./PageTenant";
import PageviewTracker from "./PageviewTracker";
import { NarrativesSlug } from "./routerUtils/types";
import ScrollManager from "./ScrollManager";
import SiteFooter from "./SiteFooter";
Expand Down Expand Up @@ -77,6 +78,7 @@ const App: React.FC = () => {
<PageNotFound default />
</Router>
<ScrollManager />
<PageviewTracker />
</Main>
<SiteFooter />
<TooltipMobile />
Expand Down
2 changes: 1 addition & 1 deletion spotlight-client/src/DataStore/RootStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default class RootStore {
userStore: UserStore;

constructor() {
makeObservable(this, { tenant: computed });
makeObservable(this, { tenant: computed, narrative: computed });

this.tenantStore = new TenantStore({ rootStore: this });

Expand Down
30 changes: 30 additions & 0 deletions spotlight-client/src/DataStore/UiStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,34 @@ export default class UiStore {

return idParts.join("::");
}

/**
* Constructs an appropriate string for the `title` tag,
* or returns undefined if the page is still loading and title cannot be determined yet
*/
get currentPageTitle(): string | undefined {
const titleParts: string[] = [];

const { tenant, narrative } = this.rootStore;

if (tenant) {
titleParts.unshift(tenant.name);

if (narrative) {
titleParts.unshift(narrative.title);
}
}

if (!titleParts.length) {
// this is valid if we are on the site homepage;
// otherwise it is an intermediate state that should not leak into reactions
if (window.location.pathname !== "/") {
return undefined;
}
}

titleParts.push("Spotlight by Recidiviz");

return titleParts.join(" — ");
}
}
37 changes: 37 additions & 0 deletions spotlight-client/src/PageviewTracker/PageviewTracker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { observer } from "mobx-react-lite";
import { useEffect } from "react";
import { useDataStore } from "../StoreProvider";

const PageviewTracker = (): null => {
const {
uiStore: { currentPageTitle },
} = useDataStore();

useEffect(() => {
if (currentPageTitle) {
document.title = currentPageTitle;
window.analytics.page();
}
}, [currentPageTitle]);

return null;
};

export default observer(PageviewTracker);
18 changes: 18 additions & 0 deletions spotlight-client/src/PageviewTracker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

export { default } from "./PageviewTracker";
8 changes: 8 additions & 0 deletions spotlight-client/src/react-app-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@

/// <reference types="react-scripts" />
/// <reference types="styled-components/cssprop" />

import { SegmentAnalytics } from "@segment/analytics.js-core";

declare global {
interface Window {
analytics: SegmentAnalytics.AnalyticsJS;
}
}
3 changes: 3 additions & 0 deletions spotlight-client/src/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import "intersection-observer";
import fetchMock from "jest-fetch-mock";
import "jest-date-mock";
import { configure } from "mobx";
// this mocks global Segment object via side effect
// (which seems to be the only way it works for some reason)
import "./testUtils/segmentMock";

// we want this mock to be available but disabled by default;
// tests should default to doing real fetches against a /spotlight-api test server
Expand Down
19 changes: 19 additions & 0 deletions spotlight-client/src/testUtils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

export * from "./testUtils";
export { default as segmentMock } from "./segmentMock";
25 changes: 25 additions & 0 deletions spotlight-client/src/testUtils/segmentMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { SegmentAnalytics } from "@segment/analytics.js-core";
import { mock } from "jest-mock-extended";

const segmentMock = mock<SegmentAnalytics.AnalyticsJS>();

window.analytics = segmentMock;

export default segmentMock;
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import { render, RenderResult } from "@testing-library/react";
import React from "react";
import { autorun } from "mobx";
import waitForLocalhost from "wait-for-localhost";
import App from "./App";
import StoreProvider from "./StoreProvider";
import App from "../App";
import StoreProvider from "../StoreProvider";

export function waitForTestServer(): Promise<void> {
return waitForLocalhost({ path: "/health", port: 3002 });
Expand Down
Loading

0 comments on commit 14d2225

Please sign in to comment.