• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / 3bc340e2-329f-4ed2-8700-adaaac8d78c8

15 Dec 2023 06:50PM CUT coverage: 73.514% (-0.1%) from 73.614%
3bc340e2-329f-4ed2-8700-adaaac8d78c8

push

circleci

jwhitlock
Use branch database with production tests

Previously, migrations tests were run with production code, branch
requirements, and branch migrations. Now they run with production
requirements, so that third-party migrations are tested as well.

This uses pytest --reuse-db to create a test database with the branch's
migrations, and then a pip install with the production code. This more
closely emulates the mixed environment during a deploy.

1962 of 2913 branches covered (0.0%)

Branch coverage included in aggregate %.

6273 of 8289 relevant lines covered (75.68%)

19.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.31
/frontend/src/components/dashboard/ProfileBanners.tsx
1
import { ReactNode } from "react";
2
import { event as gaEvent } from "react-ga";
1✔
3

4
import Image from "next/image";
1✔
5
import styles from "./ProfileBanners.module.scss";
1✔
6
import FirefoxLogo from "./images/fx-logo.svg";
1✔
7
import BundleLogo from "./images/vpn-and-relay-logo.svg";
1✔
8
import AddonIllustration from "./images/banner-addon.svg";
1✔
9
import {
10
  getBundlePrice,
11
  isBundleAvailableInCountry,
12
  RuntimeDataWithBundleAvailable,
13
} from "../../functions/getPlan";
1✔
14
import {
15
  isUsingFirefox,
16
  supportsChromeExtension,
17
  supportsFirefoxExtension,
18
} from "../../functions/userAgent";
1✔
19
import { ProfileData } from "../../hooks/api/profile";
20
import { UserData } from "../../hooks/api/user";
21
import { RuntimeData } from "../../../src/hooks/api/runtimeData";
22
import { Banner } from "../Banner";
1✔
23
import { renderDate } from "../../functions/renderDate";
1✔
24
import { SubdomainPicker } from "./SubdomainPicker";
1✔
25
import { useMinViewportWidth } from "../../hooks/mediaQuery";
1✔
26
import { AliasData } from "../../hooks/api/aliases";
27
import { useL10n } from "../../hooks/l10n";
1✔
28

29
export type Props = {
30
  profile: ProfileData;
31
  user: UserData;
32
  onCreateSubdomain: (chosenSubdomain: string) => Promise<void>;
33
  runtimeData?: RuntimeData;
34
  aliases: AliasData[];
35
};
36

37
/**
38
 * Displays relevant banners for on the user's profile page.
39
 *
40
 * Examples are:
41
 * - A banner to download Firefox if you're not on Firefox.
42
 * - A banner to download the add-on if you don't have it.
43
 * - A banner to upgrade to Premium if you don't have it but can purchase it.
44
 * - A banner to inform the user of emails that failed to be delivered.
45
 * - Anything else we might come up with in the future.
46
 *
47
 * See also {@link Banner}.
48
 */
49
export const ProfileBanners = (props: Props) => {
46✔
50
  const banners: ReactNode[] = [];
46✔
51
  const isLargeScreen = useMinViewportWidth("md");
46✔
52

53
  const bounceStatus = props.profile.bounce_status;
46✔
54
  if (bounceStatus[0]) {
46!
55
    banners.push(
×
56
      <BounceBanner
57
        key="bounce-banner"
58
        email={props.user.email}
59
        profile={props.profile}
60
      />,
61
    );
62
  }
63

64
  if (isBundleAvailableInCountry(props.runtimeData) && !props.profile.has_vpn) {
46!
65
    banners.push(
×
66
      <BundlePromoBanner
67
        key="bundle-promo"
68
        runtimeData={props.runtimeData}
69
        profileData={props.profile}
70
      />,
71
    );
72
  }
73

74
  banners.push(
46✔
75
    <SubdomainPicker
76
      key="subdomain-picker"
77
      profile={props.profile}
78
      onCreate={props.onCreateSubdomain}
79
    />,
80
  );
81

82
  // Don't show the "Get Firefox" banner if we have an extension available,
83
  // to avoid banner overload:
84
  if (!isUsingFirefox() && (!supportsChromeExtension() || !isLargeScreen)) {
46✔
85
    banners.push(<NoFirefoxBanner key="firefox-banner" />);
41✔
86
  }
87

88
  if (
46✔
89
    supportsFirefoxExtension() &&
53✔
90
    isLargeScreen &&
91
    // This identifies mock data used for demonstration purposes.
92
    // See /frontend/src/apiMocks/mockData.ts:
93
    props.profile.api_token !== "demo"
94
  ) {
95
    // This pushes a banner promoting the add-on - detecting the add-on
96
    // and determining whether to show it based on that is a bit slow,
97
    // so we'll just let the add-on hide it:
98
    banners.push(
3✔
99
      <NoAddonBanner profileData={props.profile} key="addon-banner" />,
100
    );
101
  }
102

103
  if (supportsChromeExtension() && isLargeScreen) {
46✔
104
    // This pushes a banner promoting the add-on - detecting the add-on
105
    // and determining whether to show it based on that is a bit slow,
106
    // so we'll just let the add-on hide it:
107
    banners.push(
1✔
108
      <NoChromeExtensionBanner
109
        profileData={props.profile}
110
        key="chrome-extension-banner"
111
      />,
112
    );
113
  }
114

115
  return <div className={styles["profile-banners"]}>{banners}</div>;
116
};
117

118
type BounceBannerProps = {
119
  email: string;
120
  profile: ProfileData;
121
};
122
const BounceBanner = (props: BounceBannerProps) => {
1✔
123
  const l10n = useL10n();
×
124

125
  return (
126
    <Banner type="warning" title={l10n.getString("banner-bounced-headline")}>
127
      {l10n.getFragment("banner-bounced-copy", {
128
        vars: {
129
          username: props.email,
130
          bounce_type: props.profile.bounce_status[1],
131
          date: renderDate(props.profile.next_email_try, l10n),
132
        },
133
        elems: {
134
          em: <em />,
135
        },
136
      })}
137
    </Banner>
138
  );
139
};
140

141
const NoFirefoxBanner = () => {
1✔
142
  const l10n = useL10n();
41✔
143

144
  return (
145
    <Banner
146
      type="promo"
147
      title={l10n.getString("banner-download-firefox-headline")}
148
      illustration={{
149
        img: <Image src={FirefoxLogo} alt="" width={60} height={60} />,
150
      }}
151
      cta={{
152
        target:
153
          "https://www.mozilla.org/firefox/new/?utm_source=fx-relay&utm_medium=banner&utm_campaign=download-fx",
154
        content: l10n.getString("banner-download-firefox-cta"),
155
        size: "large",
156
        gaViewPing: {
157
          category: "Download Firefox",
158
          label: "profile-banner-download-firefox",
159
        },
160
        onClick: () => {
161
          gaEvent({
×
162
            category: "Download Firefox",
163
            action: "Engage",
164
            label: "profile-banner-download-firefox",
165
          });
166
        },
167
      }}
168
    >
169
      <p>{l10n.getString("banner-download-firefox-copy-2")}</p>
170
    </Banner>
171
  );
172
};
173

174
type NoAddonBannerProps = {
175
  profileData: ProfileData;
176
};
177

178
const NoAddonBanner = (props: NoAddonBannerProps) => {
1✔
179
  const l10n = useL10n();
3✔
180

181
  return (
182
    <Banner
183
      type="promo"
184
      title={l10n.getString("banner-download-install-extension-headline")}
185
      illustration={{
186
        img: <Image src={AddonIllustration} alt="" width={60} height={60} />,
187
      }}
188
      cta={{
189
        target:
190
          "https://addons.mozilla.org/firefox/addon/private-relay/?utm_source=fx-relay&utm_medium=banner&utm_campaign=install-addon",
191
        content: l10n.getString("banner-download-install-extension-cta"),
192
        size: "large",
193
        gaViewPing: {
194
          category: "Download Extension",
195
          label: "profile-banner-download-firefox-extension",
196
        },
197
        onClick: () => {
198
          gaEvent({
×
199
            category: "Download Firefox",
200
            action: "Engage",
201
            label: "profile-banner-download-firefox-extension",
202
          });
203
        },
204
      }}
205
      dismissal={{
206
        key: `firefox-extension-banner-${props.profileData.id}`,
207
      }}
208
      hiddenWithAddon={true}
209
    >
210
      <p>{l10n.getString("banner-download-install-extension-copy-2")}</p>
211
    </Banner>
212
  );
213
};
214

215
type NoChromeExtensionBannerProps = {
216
  profileData: ProfileData;
217
};
218

219
// make dismissble
220
const NoChromeExtensionBanner = (props: NoChromeExtensionBannerProps) => {
1✔
221
  const l10n = useL10n();
1✔
222

223
  return (
224
    <Banner
225
      type="promo"
226
      title={l10n.getString(
227
        "banner-download-install-chrome-extension-headline",
228
      )}
229
      illustration={{
230
        img: <Image src={AddonIllustration} alt="" width={60} height={60} />,
231
      }}
232
      cta={{
233
        target:
234
          "https://chrome.google.com/webstore/detail/firefox-relay/lknpoadjjkjcmjhbjpcljdednccbldeb?utm_source=fx-relay&utm_medium=banner&utm_campaign=install-addon",
235
        content: l10n.getString("banner-download-install-chrome-extension-cta"),
236
        size: "large",
237
        gaViewPing: {
238
          category: "Download Extension",
239
          label: "profile-banner-download-chrome-extension",
240
        },
241
        onClick: () => {
242
          gaEvent({
×
243
            category: "Download Firefox",
244
            action: "Engage",
245
            label: "profile-banner-download-chrome-extension",
246
          });
247
        },
248
      }}
249
      hiddenWithAddon={true}
250
      dismissal={{
251
        key: `no-chrome-extension-banner-${props.profileData.id}`,
252
      }}
253
    >
254
      <p>{l10n.getString("banner-download-install-chrome-extension-copy-2")}</p>
255
    </Banner>
256
  );
257
};
258

259
type BundleBannerProps = {
260
  runtimeData: RuntimeDataWithBundleAvailable;
261
  profileData: ProfileData;
262
};
263

264
const BundlePromoBanner = (props: BundleBannerProps) => {
1✔
265
  const l10n = useL10n();
×
266

267
  return (
×
268
    <Banner
269
      key="bundle-banner"
270
      type="promo"
271
      illustration={{
272
        img: (
273
          <Image
274
            src={BundleLogo}
275
            alt=""
276
            width={120}
277
            height={60}
278
            className={styles["bundle-logo"]}
279
          />
280
        ),
281
      }}
282
      title={l10n.getString("bundle-banner-dashboard-header")}
283
      cta={{
284
        target: "/premium#pricing",
285
        size: "large",
286
        gaViewPing: {
287
          category: "Purchase Bundle button",
288
          label: "profile-banner-bundle-promo",
289
        },
290
        onClick: () => {
291
          gaEvent({
×
292
            category: "Purchase Bundle button",
293
            action: "Engage",
294
            label: "profile-banner-bundle-promo",
295
          });
296
        },
297
        content: l10n.getString("bundle-banner-dashboard-upgrade-cta"),
298
      }}
299
      dismissal={{
300
        key: `bundle-promo-banner-${props.profileData.id}`,
301
      }}
302
    >
303
      <p>
304
        {l10n.getString("bundle-banner-dashboard-body", {
305
          savings: "40%",
306
          monthly_price: getBundlePrice(props.runtimeData, l10n),
307
        })}
308
      </p>
309
    </Banner>
310
  );
311
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc