diff --git a/client/src/app.tsx b/client/src/app.tsx index 88c702a3de60..ee6eca2f61a4 100644 --- a/client/src/app.tsx +++ b/client/src/app.tsx @@ -25,6 +25,7 @@ import { useGleanPage } from "./telemetry/glean-context"; import { MainContentContainer } from "./ui/atoms/page-content"; import { Loading } from "./ui/atoms/loading"; import { Advertising } from "./advertising"; +import { HydrationData } from "../../libs/types/hydration"; const AllFlaws = React.lazy(() => import("./flaws")); const Translations = React.lazy(() => import("./translations")); @@ -108,9 +109,18 @@ function PageOrPageNotFound({ pageNotFound, children }) { ); } -export function App(appProps) { +export function App(appProps: HydrationData) { + const { pathname } = useLocation(); + const initialPathname = React.useRef(pathname); + const pageNotFound = React.useMemo( + () => + (appProps.pageNotFound || false) && initialPathname.current === pathname, + [appProps.pageNotFound, pathname] + ); + usePing(); - useGleanPage(); + useGleanPage(pageNotFound); + const localeMatch = useMatch("/:locale/*"); useEffect(() => { @@ -119,18 +129,6 @@ export function App(appProps) { document.documentElement.setAttribute("lang", locale); }, [appProps.locale, localeMatch]); - const [pageNotFound, setPageNotFound] = React.useState( - appProps.pageNotFound - ); - const { pathname } = useLocation(); - const initialPathname = React.useRef(pathname); - - React.useEffect(() => { - setPageNotFound( - appProps.pageNotFound && initialPathname.current === pathname - ); - }, [appProps.pageNotFound, pathname]); - const isServer = useIsServer(); // When preparing a build for use in the NPM package, CRUD_MODE is always true. diff --git a/client/src/contributor-spotlight/index.tsx b/client/src/contributor-spotlight/index.tsx index e42e9b7e3c55..f264c03606c6 100644 --- a/client/src/contributor-spotlight/index.tsx +++ b/client/src/contributor-spotlight/index.tsx @@ -3,7 +3,7 @@ import { useParams } from "react-router-dom"; import useSWR from "swr"; import { CRUD_MODE } from "../env"; -import { HydrationData } from "../types/hydration"; +import { HydrationData } from "../../../libs/types/hydration"; import { GetInvolved } from "../ui/molecules/get_involved"; import { Quote } from "../ui/molecules/quote"; diff --git a/client/src/homepage/contributor-spotlight/index.tsx b/client/src/homepage/contributor-spotlight/index.tsx index 06869d1fd3ec..84aa2fc12100 100644 --- a/client/src/homepage/contributor-spotlight/index.tsx +++ b/client/src/homepage/contributor-spotlight/index.tsx @@ -1,6 +1,6 @@ import useSWR from "swr"; import { CRUD_MODE } from "../../env"; -import { HydrationData } from "../../types/hydration"; +import { HydrationData } from "../../../../libs/types/hydration"; import { Icon } from "../../ui/atoms/icon"; import Mandala from "../../ui/molecules/mandala"; diff --git a/client/src/homepage/featured-articles/index.tsx b/client/src/homepage/featured-articles/index.tsx index 296a2410262e..19c785f6a2f2 100644 --- a/client/src/homepage/featured-articles/index.tsx +++ b/client/src/homepage/featured-articles/index.tsx @@ -1,6 +1,6 @@ import useSWR from "swr"; import { CRUD_MODE } from "../../env"; -import { HydrationData } from "../../types/hydration"; +import { HydrationData } from "../../../../libs/types/hydration"; import "./index.scss"; diff --git a/client/src/homepage/latest-news/index.tsx b/client/src/homepage/latest-news/index.tsx index 1b17a45b8e12..8d2affa34e9d 100644 --- a/client/src/homepage/latest-news/index.tsx +++ b/client/src/homepage/latest-news/index.tsx @@ -2,7 +2,7 @@ import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import useSWR from "swr"; import { CRUD_MODE } from "../../env"; -import { HydrationData } from "../../types/hydration"; +import { HydrationData } from "../../../../libs/types/hydration"; import { NewsItem } from "../../../../libs/types/document"; import "./index.scss"; diff --git a/client/src/homepage/recent-contributions/index.tsx b/client/src/homepage/recent-contributions/index.tsx index f233f493beea..f4b91f31d79a 100644 --- a/client/src/homepage/recent-contributions/index.tsx +++ b/client/src/homepage/recent-contributions/index.tsx @@ -2,7 +2,7 @@ import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import useSWR from "swr"; import { CRUD_MODE } from "../../env"; -import { HydrationData } from "../../types/hydration"; +import { HydrationData } from "../../../../libs/types/hydration"; import "./index.scss"; diff --git a/client/src/telemetry/generated/page.ts b/client/src/telemetry/generated/page.ts index 796171479b37..63ab4eb20b04 100644 --- a/client/src/telemetry/generated/page.ts +++ b/client/src/telemetry/generated/page.ts @@ -5,6 +5,7 @@ // AUTOGENERATED BY glean_parser v6.2.1. DO NOT EDIT. DO NOT COMMIT. import UrlMetricType from "@mozilla/glean/private/metrics/url"; +import StringMetricType from "@mozilla/glean/private/metrics/string"; /** * The URL path of the page that was viewed. @@ -31,3 +32,16 @@ export const referrer = new UrlMetricType({ lifetime: "application", disabled: false, }); + +/** + * The http status of the page. + * + * Generated from `page.http_status`. + */ +export const httpStatus = new StringMetricType({ + category: "page", + name: "http_status", + sendInPings: ["action", "page"], + lifetime: "application", + disabled: false, +}); diff --git a/client/src/telemetry/glean-context.tsx b/client/src/telemetry/glean-context.tsx index 95d087e7e44a..1973e81955ea 100644 --- a/client/src/telemetry/glean-context.tsx +++ b/client/src/telemetry/glean-context.tsx @@ -12,10 +12,12 @@ import { handleSidebarClick } from "./sidebar-click"; import { VIEWPORT_BREAKPOINTS } from "./constants"; export type ViewportBreakpoint = "xs" | "sm" | "md" | "lg" | "xl" | "xxl"; +export type HTTPStatus = "200" | "404"; export type PageProps = { referrer: string | undefined; path: string | undefined; + httpStatus: HTTPStatus; subscriptionType: string; geo: string | undefined; userAgent: string | undefined; @@ -77,6 +79,7 @@ function glean(): GleanAnalytics { if (page.referrer) { pageMetric.referrer.set(page.referrer); } + pageMetric.httpStatus.set(page.httpStatus); if (page.geo) { navigatorMetric.geo.set(page.geo); } @@ -147,7 +150,7 @@ export function useGlean() { return React.useContext(GleanContext); } -export function useGleanPage() { +export function useGleanPage(pageNotFound: boolean) { const loc = useLocation(); const userData = useUserData(); const path = useRef(null); @@ -156,6 +159,8 @@ export function useGleanPage() { const submit = gleanAnalytics.page({ path: window?.location.toString(), referrer: document?.referrer, + // on port 3000 this will always return "200": + httpStatus: pageNotFound ? "404" : "200", userAgent: navigator?.userAgent, geo: userData?.geo?.country, subscriptionType: userData?.subscriptionType || "anonymous", @@ -171,7 +176,7 @@ export function useGleanPage() { path.current = loc.pathname; submit(); } - }, [loc.pathname, userData]); + }, [loc.pathname, userData, pageNotFound]); } export function useGleanClick() { diff --git a/client/src/telemetry/metrics.yaml b/client/src/telemetry/metrics.yaml index 16943e70e849..eed683b78a1a 100644 --- a/client/src/telemetry/metrics.yaml +++ b/client/src/telemetry/metrics.yaml @@ -37,6 +37,23 @@ page: notification_emails: - mdn-team@mozilla.com expires: 2023-09-05 + http_status: + type: string + description: | + The HTTP status code of the page. + lifetime: application + send_in_pings: + - page + - action + data_sensitivity: + - technical + bugs: + - https://mozilla-hub.atlassian.net/browse/MP-285 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1822124 + notification_emails: + - mdn-team@mozilla.com + expires: 2023-09-05 navigator: geo: diff --git a/client/src/types/hydration.ts b/client/src/types/hydration.ts deleted file mode 100644 index fa61b66a3617..000000000000 --- a/client/src/types/hydration.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface HydrationData { - hyData: Type; -} - -export type { HydrationData }; diff --git a/libs/types/hydration.ts b/libs/types/hydration.ts new file mode 100644 index 000000000000..497605880923 --- /dev/null +++ b/libs/types/hydration.ts @@ -0,0 +1,11 @@ +interface HydrationData { + hyData?: T; + doc?: any; + pageNotFound?: boolean; + pageTitle?: any; + possibleLocales?: any; + locale?: any; + noIndexing?: any; +} + +export type { HydrationData }; diff --git a/ssr/render.ts b/ssr/render.ts index 4e7a37188e8e..9daa648dce08 100644 --- a/ssr/render.ts +++ b/ssr/render.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { renderToString } from "react-dom/server"; +import { HydrationData } from "../libs/types/hydration"; import { DEFAULT_LOCALE } from "../libs/constants"; import { ALWAYS_ALLOW_ROBOTS, BUILD_OUT_ROOT } from "../libs/env"; @@ -142,16 +143,6 @@ function* extractCSSURLs(css, filterFunction) { } } -interface HydrationData { - doc?: any; - pageNotFound?: boolean; - hyData?: any; - pageTitle?: any; - possibleLocales?: any; - locale?: any; - noIndexing?: any; -} - export default function render( renderApp, {