Skip to content

Commit

Permalink
Merge pull request #4787 from mozilla/MNTOR-3358
Browse files Browse the repository at this point in the history
Free scan CTA experiment (MNTOR-3341)
  • Loading branch information
flozia authored Jul 15, 2024
2 parents ce4fe1f + 0ced7cd commit 9a793b9
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 28 deletions.
34 changes: 34 additions & 0 deletions config/nimbus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,37 @@ features:
value: { "enabled": false }
- channel: production
value: { "enabled": false }
landing-page-free-scan-cta:
description: Landing page free scan CTA
variables:
enabled:
description: If the feature is enabled
type: Boolean
default: false
variant:
description: The CTA variant to show
type: FreeScanCtaType
default: ctaWithEmail
defaults:
- channel: local
value: {
"enabled": true,
"variant": ctaOnly,
}
- channel: staging
value: {
"enabled": false,
"variant": ctaWithEmail,
}
- channel: production
value: {
"enabled": false,
"variant": ctaWithEmail,
}
enums:
FreeScanCtaType:
description: An enum of free scan CTA types
variants:
ctaWithEmail: Show a CTA button with an optional email input
ctaOnly: Only show a CTA button with the label “Get free scan”
ctaOnlyAlternativeLabel: Only show a CTA button with the label “Sign in to get free scan”
1 change: 1 addition & 0 deletions locales/en/landing-all.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ landing-all-hero-lead = We scan data breaches to see if your data has been leake
landing-all-hero-emailform-input-placeholder = [email protected]
landing-all-hero-emailform-input-label = Enter your email address to check for data breach exposures.
landing-all-hero-emailform-submit-label = Get free scan
landing-all-hero-emailform-submit-sign-in-label = Sign in to get free scan
# This is a label underneath a big number "14" - it's an image that demos Monitor.
landing-all-hero-image-chart-label = exposures
Expand Down
120 changes: 120 additions & 0 deletions src/app/(proper_react)/(redesign)/(public)/FreeScanCta.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/* 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 { signIn } from "next-auth/react";
import { useCookies } from "react-cookie";
import { SignUpForm, Props as SignUpFormProps } from "./SignUpForm";
import { TelemetryButton } from "../../../components/client/TelemetryButton";
import { modifyAttributionsForUrlSearchParams } from "../../../functions/universal/attributions";
import { ExperimentData } from "../../../../telemetry/generated/nimbus/experiments";
import { useL10n } from "../../../hooks/l10n";
import { WaitlistCta } from "./ScanLimit";

export type Props = {
eligibleForPremium: boolean;
signUpCallbackUrl: string;
isHero?: boolean;
eventId: {
cta: string;
field?: string;
};
scanLimitReached: boolean;
placeholder?: string;
};

export function getAttributionSearchParams({
cookies,
emailInput,
experimentData,
}: {
cookies: {
attributionsFirstTouch?: string;
};
emailInput?: string;
experimentData?: ExperimentData;
}) {
const attributionSearchParams = modifyAttributionsForUrlSearchParams(
new URLSearchParams(cookies.attributionsFirstTouch),
{
entrypoint: "monitor.mozilla.org-monitor-product-page",
form_type: typeof emailInput === "string" ? "email" : "button",
service: process.env.OAUTH_CLIENT_ID as string,
...(emailInput && { email: emailInput }),
...(experimentData &&
experimentData["landing-page-free-scan-cta"].enabled && {
entrypoint_experiment: "landing-page-free-scan-cta",
entrypoint_variation:
experimentData["landing-page-free-scan-cta"].variant,
}),
},
{
utm_source: "product",
utm_medium: "monitor",
utm_campaign: "get_free_scan",
},
);

return attributionSearchParams.toString();
}

export const FreeScanCta = (
props: SignUpFormProps & {
experimentData: ExperimentData;
},
) => {
const l10n = useL10n();
const [cookies] = useCookies(["attributionsFirstTouch"]);
if (
!props.experimentData["landing-page-free-scan-cta"].enabled ||
props.experimentData["landing-page-free-scan-cta"].variant ===
"ctaWithEmail"
) {
return (
<SignUpForm
scanLimitReached={props.scanLimitReached}
isHero={props.isHero}
eligibleForPremium={props.eligibleForPremium}
signUpCallbackUrl={props.signUpCallbackUrl}
eventId={props.eventId}
experimentData={props.experimentData}
/>
);
}

return props.scanLimitReached ? (
<WaitlistCta />
) : (
<div>
<TelemetryButton
variant="primary"
event={{
module: "ctaButton",
name: "click",
data: {
button_id: `${props.eventId.cta}-${props.experimentData["landing-page-free-scan-cta"].variant}`,
},
}}
onPress={() => {
void signIn(
"fxa",
{ callbackUrl: props.signUpCallbackUrl },
getAttributionSearchParams({
cookies,
experimentData: props.experimentData,
}),
);
}}
>
{l10n.getString(
props.experimentData["landing-page-free-scan-cta"].variant ===
"ctaOnly"
? "landing-all-hero-emailform-submit-label"
: "landing-all-hero-emailform-submit-sign-in-label",
)}
</TelemetryButton>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import type { Meta, StoryObj } from "@storybook/react";
import { View, Props as ViewProps } from "./LandingView";
import { getL10n } from "../../../functions/l10n/storybookAndJest";
import { PublicShell } from "./PublicShell";
import { defaultExperimentData } from "../../../../telemetry/generated/nimbus/experiments";

const meta: Meta<typeof View> = {
title: "Pages/Public/Landing page",
component: (props: ViewProps) => (
<PublicShell l10n={getL10n("en")} countryCode={props.countryCode}>
<View {...props} />
<View
{...props}
experimentData={props.experimentData ?? defaultExperimentData}
/>
</PublicShell>
),
args: {
Expand Down
195 changes: 195 additions & 0 deletions src/app/(proper_react)/(redesign)/(public)/LandingView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Meta, {
LandingUsScanLimit,
} from "./LandingView.stories";
import { deleteAllCookies } from "../../../functions/client/deleteAllCookies";
import { defaultExperimentData } from "../../../../telemetry/generated/nimbus/experiments";

jest.mock("next-auth/react", () => {
return {
Expand Down Expand Up @@ -833,6 +834,200 @@ describe("When Premium is available", () => {
});
});

describe("Free scan CTA experiment", () => {
it("shows the CTA button with email input if the experiment disabled", () => {
const ComposedDashboard = composeStory(LandingUs, Meta);
render(
<ComposedDashboard
experimentData={{
...defaultExperimentData,
"landing-page-free-scan-cta": {
...defaultExperimentData["landing-page-free-scan-cta"],
enabled: false,
},
}}
/>,
);

const inputField = screen.getAllByLabelText(
"Enter your email address to check for data breach exposures and sites selling your info.",
);
expect(inputField[0]).toBeInTheDocument();

const submitButton = screen.getAllByRole("button", {
name: "Get free scan",
});
expect(submitButton[0]).toBeInTheDocument();
});

it("shows the CTA button with email input for the variant `ctaWithEmail` if the experiment is enabled", () => {
const ComposedDashboard = composeStory(LandingUs, Meta);
render(
<ComposedDashboard
experimentData={{
...defaultExperimentData,
"landing-page-free-scan-cta": {
enabled: true,
variant: "ctaWithEmail",
},
}}
/>,
);

const inputField = screen.getAllByLabelText(
"Enter your email address to check for data breach exposures and sites selling your info.",
);
expect(inputField[0]).toBeInTheDocument();

const submitButton = screen.getAllByRole("button", {
name: "Get free scan",
});
expect(submitButton[0]).toBeInTheDocument();
});

it("shows the CTA button only for the variant `ctaOnly` if the experiment is enabled", () => {
const ComposedDashboard = composeStory(LandingUs, Meta);
render(
<ComposedDashboard
experimentData={{
...defaultExperimentData,
"landing-page-free-scan-cta": {
enabled: true,
variant: "ctaOnly",
},
}}
/>,
);

const inputField = screen.queryAllByTestId("signup-form-input");
expect(inputField.length).toBe(0);

const submitButton = screen.getAllByRole("button", {
name: "Get free scan",
});
expect(submitButton[0]).toBeInTheDocument();
});

it("shows the CTA button only with an alternative label for the variant `ctaOnlyAlternativeLabel` if the experiment is enabled", () => {
const ComposedDashboard = composeStory(LandingUs, Meta);
render(
<ComposedDashboard
experimentData={{
...defaultExperimentData,
"landing-page-free-scan-cta": {
enabled: true,
variant: "ctaOnlyAlternativeLabel",
},
}}
/>,
);

const inputField = screen.queryAllByTestId("signup-form-input");
expect(inputField.length).toBe(0);

const submitButton = screen.getAllByRole("button", {
name: "Sign in to get free scan",
});
expect(submitButton[0]).toBeInTheDocument();
});

it("shows the waitlist CTA when the scan limit is reached", () => {
const ComposedDashboard = composeStory(LandingUsScanLimit, Meta);
render(
<ComposedDashboard
experimentData={{
...defaultExperimentData,
"landing-page-free-scan-cta": {
enabled: true,
variant: "ctaOnly",
},
}}
/>,
);
const waitlistCta = screen.getAllByRole("link", {
name: "Join waitlist",
});
expect(waitlistCta[0]).toBeInTheDocument();
});

it("sends telemetry for the different experiment variants", async () => {
const mockedRecord = useTelemetry();
const user = userEvent.setup();
const ComposedDashboard = composeStory(LandingUs, Meta);
render(
<ComposedDashboard
experimentData={{
...defaultExperimentData,
"landing-page-free-scan-cta": {
enabled: true,
variant: "ctaOnly",
},
}}
/>,
);

// jsdom will complain about not being able to navigate to a different page
// after clicking the link; suppress that error, as it's not relevant to the
// test:
jest.spyOn(console, "error").mockImplementation(() => undefined);

const submitButton = screen.getAllByRole("button", {
name: "Get free scan",
});
await user.click(submitButton[0]);

expect(mockedRecord).toHaveBeenCalledWith(
"ctaButton",
"click",
expect.objectContaining({ button_id: "clicked_get_scan_header-ctaOnly" }),
);
});

it("passes the expected URL to the identity provider", async () => {
const user = userEvent.setup();
const ComposedDashboard = composeStory(LandingUs, Meta);
render(
<ComposedDashboard
experimentData={{
...defaultExperimentData,
"landing-page-free-scan-cta": {
enabled: true,
variant: "ctaWithEmail",
},
}}
/>,
);

const inputField = screen.getAllByLabelText(
"Enter your email address to check for data breach exposures and sites selling your info.",
);
await user.type(inputField[0], "[email protected]");

const submitButton = screen.getAllByRole("button", {
name: "Get free scan",
});
await user.click(submitButton[0]);

expect(signIn).toHaveBeenCalledWith(
"fxa",
expect.any(Object),
expect.stringContaining(
[
"entrypoint=monitor.mozilla.org-monitor-product-page",
"form_type=email",
"service=edd29a80019d61a1",
"email=mail%40example.com",
"entrypoint_experiment=landing-page-free-scan-cta",
"entrypoint_variation=ctaWithEmail",
"utm_source=product",
"utm_medium=monitor",
"utm_campaign=get_free_scan",
].join("&"),
),
);
});
});

it("does not show a confirmaton message if the user has just deleted their account", () => {
document.cookie = "justDeletedAccount=justDeletedAccount; max-age=0";

Expand Down
Loading

0 comments on commit 9a793b9

Please sign in to comment.