feat: Add metrics flow context provider
This commit is contained in:
Родитель
de3c91fa01
Коммит
3fa1b12b0f
1
.env
1
.env
|
@ -37,6 +37,7 @@ FXA_SETTINGS_URL=https://accounts.stage.mozaws.net/settings
|
|||
OAUTH_CLIENT_ID=edd29a80019d61a1
|
||||
OAUTH_CLIENT_SECRET=get-this-from-groovecoder-or-fxmonitor-engineering
|
||||
OAUTH_AUTHORIZATION_URI=https://oauth.stage.mozaws.net/v1/authorization
|
||||
OAUTH_METRICS_FLOW_URI=https://accounts.stage.mozaws.net/metrics-flow
|
||||
OAUTH_PROFILE_URI=https://profile.stage.mozaws.net/v1/profile
|
||||
OAUTH_TOKEN_URI=https://oauth.stage.mozaws.net/v1/token
|
||||
OAUTH_ACCOUNT_URI = "https://oauth.accounts.firefox.com/v1"
|
||||
|
|
|
@ -16,7 +16,8 @@ Sentry.init({
|
|||
tracesSampleRate: ["local"].includes(getEnvironment()) ? 1.0 : 0.1,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: getEnvironment() !== "production",
|
||||
// debug: getEnvironment() !== "production",
|
||||
debug: false,
|
||||
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ import { modifyAttributionsForUrlSearchParams } from "../../../functions/univers
|
|||
import { ExperimentData } from "../../../../telemetry/generated/nimbus/experiments";
|
||||
import { useL10n } from "../../../hooks/l10n";
|
||||
import { WaitlistCta } from "./ScanLimit";
|
||||
import { PublicEnvContext } from "../../../../contextProviders/public-env";
|
||||
import { useContext } from "react";
|
||||
import { AccountsMetricsFlowContext } from "../../../../contextProviders/accounts-metrics-flow";
|
||||
|
||||
export type Props = {
|
||||
eligibleForPremium: boolean;
|
||||
|
@ -30,13 +30,11 @@ export type Props = {
|
|||
export function getAttributionSearchParams({
|
||||
cookies,
|
||||
emailInput,
|
||||
publicEnv,
|
||||
experimentData,
|
||||
}: {
|
||||
cookies: {
|
||||
attributionsFirstTouch?: string;
|
||||
};
|
||||
publicEnv: Record<string, string>;
|
||||
emailInput?: string;
|
||||
experimentData?: ExperimentData;
|
||||
}) {
|
||||
|
@ -45,7 +43,7 @@ export function getAttributionSearchParams({
|
|||
{
|
||||
entrypoint: "monitor.mozilla.org-monitor-product-page",
|
||||
form_type: typeof emailInput === "string" ? "email" : "button",
|
||||
service: publicEnv.OAUTH_CLIENT_ID,
|
||||
service: process.env.OAUTH_CLIENT_ID as string,
|
||||
...(emailInput && { email: emailInput }),
|
||||
...(experimentData &&
|
||||
experimentData["landing-page-free-scan-cta"].enabled && {
|
||||
|
@ -71,7 +69,8 @@ export const FreeScanCta = (
|
|||
) => {
|
||||
const l10n = useL10n();
|
||||
const [cookies] = useCookies(["attributionsFirstTouch"]);
|
||||
const publicEnv = useContext(PublicEnvContext);
|
||||
const accountsMetricsFlowContext = useContext(AccountsMetricsFlowContext);
|
||||
|
||||
if (
|
||||
!props.experimentData["landing-page-free-scan-cta"].enabled ||
|
||||
props.experimentData["landing-page-free-scan-cta"].variant ===
|
||||
|
@ -93,7 +92,9 @@ export const FreeScanCta = (
|
|||
<WaitlistCta />
|
||||
) : (
|
||||
<div>
|
||||
<pre>{JSON.stringify(accountsMetricsFlowContext, null, 2)}</pre>
|
||||
<TelemetryButton
|
||||
disabled={accountsMetricsFlowContext.loading}
|
||||
variant="primary"
|
||||
event={{
|
||||
module: "ctaButton",
|
||||
|
@ -109,7 +110,6 @@ export const FreeScanCta = (
|
|||
getAttributionSearchParams({
|
||||
cookies,
|
||||
experimentData: props.experimentData,
|
||||
publicEnv,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -55,7 +55,10 @@ import {
|
|||
import { getLocale } from "../../../functions/universal/getLocale";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useTelemetry } from "../../../hooks/useTelemetry";
|
||||
import { CONST_ONEREP_DATA_BROKER_COUNT } from "../../../../constants";
|
||||
import {
|
||||
CONST_ONEREP_DATA_BROKER_COUNT,
|
||||
CONST_URL_MONITOR_LANDING_PAGE_ID,
|
||||
} from "../../../../constants";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { modifyAttributionsForUrlSearchParams } from "../../../functions/universal/attributions";
|
||||
import { TelemetryButton } from "../../../components/client/TelemetryButton";
|
||||
|
@ -98,7 +101,7 @@ export const PlansTable = (props: Props & ScanLimitProp) => {
|
|||
newSearchParam = modifyAttributionsForUrlSearchParams(
|
||||
newSearchParam,
|
||||
{
|
||||
entrypoint: "monitor.mozilla.org-monitor-product-page",
|
||||
entrypoint: CONST_URL_MONITOR_LANDING_PAGE_ID,
|
||||
form_type: "button",
|
||||
data_cta_position: "pricing",
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use client";
|
||||
|
||||
import { FormEventHandler, useContext, useId, useState } from "react";
|
||||
import { FormEventHandler, useId, useState } from "react";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useL10n } from "../../../hooks/l10n";
|
||||
import { Button } from "../../../components/client/Button";
|
||||
|
@ -13,9 +13,9 @@ import { useTelemetry } from "../../../hooks/useTelemetry";
|
|||
import { VisuallyHidden } from "../../../components/server/VisuallyHidden";
|
||||
import { WaitlistCta } from "./ScanLimit";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { getAttributionSearchParams } from "./FreeScanCta";
|
||||
import { ExperimentData } from "../../../../telemetry/generated/nimbus/experiments";
|
||||
import { PublicEnvContext } from "../../../../contextProviders/public-env";
|
||||
import { CONST_URL_MONITOR_LANDING_PAGE_ID } from "../../../../constants";
|
||||
import { getFreeScanSearchParams } from "../../../functions/universal/getFreeScanSearchParams";
|
||||
|
||||
export type Props = {
|
||||
eligibleForPremium: boolean;
|
||||
|
@ -36,18 +36,16 @@ export const SignUpForm = (props: Props) => {
|
|||
const [emailInput, setEmailInput] = useState("");
|
||||
const record = useTelemetry();
|
||||
const [cookies] = useCookies(["attributionsFirstTouch"]);
|
||||
const publicEnv = useContext(PublicEnvContext);
|
||||
|
||||
const onSubmit: FormEventHandler = (event) => {
|
||||
event.preventDefault();
|
||||
void signIn(
|
||||
"fxa",
|
||||
{ callbackUrl: props.signUpCallbackUrl },
|
||||
getAttributionSearchParams({
|
||||
getFreeScanSearchParams({
|
||||
cookies,
|
||||
emailInput,
|
||||
experimentData: props.experimentData,
|
||||
publicEnv,
|
||||
emailInput: "",
|
||||
entrypoint: CONST_URL_MONITOR_LANDING_PAGE_ID,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,10 +14,14 @@ import {
|
|||
import { getEnabledFeatureFlags } from "../../../../db/tables/featureFlags";
|
||||
import { getL10n } from "../../../functions/l10n/serverComponents";
|
||||
import { View } from "./LandingView";
|
||||
import { CONST_DAY_MILLISECONDS } from "../../../../constants";
|
||||
import {
|
||||
CONST_DAY_MILLISECONDS,
|
||||
CONST_URL_MONITOR_LANDING_PAGE_ID,
|
||||
} from "../../../../constants";
|
||||
import { getExperimentationId } from "../../../functions/server/getExperimentationId";
|
||||
import { getExperiments } from "../../../functions/server/getExperiments";
|
||||
import { getLocale } from "../../../functions/universal/getLocale";
|
||||
import { AccountsMetricsFlowProvider } from "../../../../contextProviders/accounts-metrics-flow";
|
||||
|
||||
type Props = {
|
||||
searchParams: {
|
||||
|
@ -51,13 +55,29 @@ export default async function Page({ searchParams }: Props) {
|
|||
typeof oneRepActivations === "undefined" ||
|
||||
oneRepActivations > monthlySubscribersQuota;
|
||||
return (
|
||||
<View
|
||||
eligibleForPremium={eligibleForPremium}
|
||||
l10n={getL10n()}
|
||||
countryCode={countryCode}
|
||||
scanLimitReached={scanLimitReached}
|
||||
enabledFlags={enabledFlags}
|
||||
experimentData={experimentData}
|
||||
/>
|
||||
<AccountsMetricsFlowProvider
|
||||
enabled={experimentData["landing-page-free-scan-cta"].enabled}
|
||||
metricsFlowParams={{
|
||||
entrypoint: CONST_URL_MONITOR_LANDING_PAGE_ID,
|
||||
entrypoint_experiment: "landing-page-free-scan-cta",
|
||||
entrypoint_variation:
|
||||
experimentData["landing-page-free-scan-cta"].variant,
|
||||
form_type:
|
||||
experimentData["landing-page-free-scan-cta"].variant ===
|
||||
"ctaWithEmail"
|
||||
? "email"
|
||||
: "button",
|
||||
service: process.env.OAUTH_CLIENT_ID as string,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
eligibleForPremium={eligibleForPremium}
|
||||
l10n={getL10n()}
|
||||
countryCode={countryCode}
|
||||
scanLimitReached={scanLimitReached}
|
||||
enabledFlags={enabledFlags}
|
||||
experimentData={experimentData}
|
||||
/>
|
||||
</AccountsMetricsFlowProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { MetricFlowData } from "../../../functions/universal/getFreeScanSearchParams";
|
||||
|
||||
async function fetchMetricsFlowParams(searchParams: URLSearchParams) {
|
||||
const endpoint = new URL(process.env.OAUTH_METRICS_FLOW_URI as string);
|
||||
for (const [key, value] of Array.from(searchParams)) {
|
||||
if (value) {
|
||||
endpoint.searchParams.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint.href, {
|
||||
...(process.env.APP_ENV !== "local" && {
|
||||
headers: {
|
||||
origin: process.env.SERVER_URL as string,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Fetching metrics flow parameters failed with ${response.status}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data: MetricFlowData = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const flowData = await fetchMetricsFlowParams(searchParams);
|
||||
return NextResponse.json({ success: true, flowData }, { status: 200 });
|
||||
} catch (e) {
|
||||
return NextResponse.json({ success: false }, { status: 500 });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { modifyAttributionsForUrlSearchParams } from "../universal/attributions";
|
||||
|
||||
export type SearchParamArgs = {
|
||||
cookies: {
|
||||
attributionsFirstTouch?: string;
|
||||
};
|
||||
emailInput?: string;
|
||||
metricsFlowData?: MetricFlowData;
|
||||
};
|
||||
|
||||
export type MetricFlowParams = {
|
||||
entrypointExperiment: string;
|
||||
entrypointVariation: string;
|
||||
entrypoint: string;
|
||||
};
|
||||
|
||||
export type MetricFlowData = {
|
||||
deviceId: string;
|
||||
flowId: string;
|
||||
flowBeginTime: number;
|
||||
};
|
||||
|
||||
export function getFreeScanSearchParams({
|
||||
cookies,
|
||||
emailInput,
|
||||
entrypoint,
|
||||
metricsFlowData,
|
||||
}: SearchParamArgs &
|
||||
Omit<MetricFlowParams, "entrypointExperiment" | "entrypointVariation">) {
|
||||
const attributionSearchParams = modifyAttributionsForUrlSearchParams(
|
||||
new URLSearchParams(cookies.attributionsFirstTouch),
|
||||
{
|
||||
entrypoint,
|
||||
form_type: typeof emailInput === "string" ? "email" : "button",
|
||||
...(emailInput && { email: emailInput }),
|
||||
...(metricsFlowData && {
|
||||
device_id: metricsFlowData.deviceId,
|
||||
flow_id: metricsFlowData.flowId,
|
||||
flow_begin_time: metricsFlowData.flowBeginTime.toString(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
utm_source: "product",
|
||||
utm_medium: "monitor",
|
||||
utm_campaign: "get_free_scan",
|
||||
},
|
||||
);
|
||||
|
||||
return attributionSearchParams.toString();
|
||||
}
|
|
@ -19,7 +19,6 @@ import StripeScript from "./components/client/StripeScript";
|
|||
|
||||
// DO NOT ADD SECRETS: Env variables added here become public.
|
||||
const PUBLIC_ENVS = {
|
||||
OAUTH_CLIENT_ID: process.env.OAUTH_CLIENT_ID ?? "",
|
||||
PUBLIC_APP_ENV: process.env.APP_ENV ?? "",
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -31,3 +31,5 @@ export const CONST_URL_DATA_PRIVACY_PETITION_BANNER =
|
|||
export const CONST_URL_MONITOR_GITHUB =
|
||||
"https://github.com/mozilla/blurts-server";
|
||||
export const CONST_DAY_MILLISECONDS = 24 * 60 * 60 * 1000;
|
||||
export const CONST_URL_MONITOR_LANDING_PAGE_ID =
|
||||
"monitor.mozilla.org-monitor-product-page";
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use client";
|
||||
|
||||
import { ReactNode, createContext, useEffect, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { MetricFlowData } from "../app/functions/universal/getFreeScanSearchParams";
|
||||
|
||||
interface SessionProviderProps {
|
||||
children: ReactNode;
|
||||
enabled: boolean;
|
||||
metricsFlowParams: {
|
||||
entrypoint: string;
|
||||
entrypoint_experiment: string;
|
||||
entrypoint_variation: string;
|
||||
form_type: string;
|
||||
service: string;
|
||||
};
|
||||
}
|
||||
|
||||
type ContextValues = {
|
||||
flowData: MetricFlowData | null;
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export const AccountsMetricsFlowContext = createContext<ContextValues>({
|
||||
flowData: null,
|
||||
loading: false,
|
||||
});
|
||||
|
||||
export const AccountsMetricsFlowProvider = ({
|
||||
children,
|
||||
enabled,
|
||||
metricsFlowParams,
|
||||
}: SessionProviderProps) => {
|
||||
const [flowData, setFlowData] = useState<ContextValues["flowData"]>(null);
|
||||
const [loading, setLoading] = useState(enabled);
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchMetricsFlowData() {
|
||||
const updatedSearchParams = new URLSearchParams(searchParams.toString());
|
||||
for (const key in metricsFlowParams) {
|
||||
const value = metricsFlowParams[key as keyof typeof metricsFlowParams];
|
||||
if (value) {
|
||||
updatedSearchParams.set(key, value);
|
||||
}
|
||||
}
|
||||
const queryString =
|
||||
updatedSearchParams.size > 0
|
||||
? `?${updatedSearchParams.toString()}`
|
||||
: "";
|
||||
const response = await fetch(
|
||||
`/api/v1/accounts-metrics-flow${queryString}`,
|
||||
);
|
||||
const data: {
|
||||
success: boolean;
|
||||
flowData?: MetricFlowData;
|
||||
} = await response.json();
|
||||
|
||||
setFlowData(data.flowData ?? null);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
void fetchMetricsFlowData();
|
||||
}
|
||||
}, [enabled, metricsFlowParams, searchParams]);
|
||||
|
||||
return (
|
||||
<AccountsMetricsFlowContext.Provider value={{ flowData, loading }}>
|
||||
{children}
|
||||
</AccountsMetricsFlowContext.Provider>
|
||||
);
|
||||
};
|
Загрузка…
Ссылка в новой задаче