From 7dad6741e87881520930af13492bd16ba4f157db Mon Sep 17 00:00:00 2001 From: Kaitlyn Andres Date: Mon, 28 Oct 2024 17:33:02 -0400 Subject: [PATCH 01/11] MNTOR 3723 - Move reduce statements into a util function (#5253) * MNTOR-3723 - Fix wrong state for users for email data point count * MNTOR-3723 - Fix wrong state for users for email data point count * remove inference * rename func --- src/emails/components/EmailDataPointCount.tsx | 17 +++++++---------- .../functions/reduceSanitizedDataPoints.ts | 13 +++++++++++++ .../MonthlyActivityFreeEmail.tsx | 15 +++++++-------- 3 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 src/emails/functions/reduceSanitizedDataPoints.ts diff --git a/src/emails/components/EmailDataPointCount.tsx b/src/emails/components/EmailDataPointCount.tsx index 3a927ebfa0..eee9abf9a6 100644 --- a/src/emails/components/EmailDataPointCount.tsx +++ b/src/emails/components/EmailDataPointCount.tsx @@ -10,6 +10,7 @@ import { DashboardSummary } from "../../app/functions/server/dashboard"; import { getSignupLocaleCountry } from "../functions/getSignupLocaleCountry"; import { isEligibleForPremium } from "../../app/functions/universal/premium"; import { SanitizedSubscriberRow } from "../../app/functions/server/sanitize"; +import { sumSanitizedDataPoints } from "../functions/reduceSanitizedDataPoints"; type Props = { l10n: ExtendedReactLocalization; @@ -24,14 +25,6 @@ export const DataPointCount = (props: Props) => { const assumedCountryCode = getSignupLocaleCountry(props.subscriber); const unresolvedDataBreaches = props.dataSummary.dataBreachUnresolvedNum; - const sumOfUnresolvedDataPoints = - props.dataSummary.unresolvedSanitizedDataPoints.reduce( - (total, dataPointSummary) => { - return total + Object.values(dataPointSummary)[0]; - }, - 0, - ); - const hasRunFreeScan = typeof props.subscriber.onerep_profile_id === "number"; const utmContentSuffix = isEligibleForPremium(assumedCountryCode) ? "-us" @@ -73,7 +66,9 @@ export const DataPointCount = (props: Props) => { line-height="68px" > {hasRunFreeScan - ? sumOfUnresolvedDataPoints + ? sumSanitizedDataPoints( + props.dataSummary.unresolvedSanitizedDataPoints, + ) : unresolvedDataBreaches} { : "email-monthly-report-no-scan-results-data-points-label", { data_point_count: hasRunFreeScan - ? sumOfUnresolvedDataPoints + ? sumSanitizedDataPoints( + props.dataSummary.unresolvedSanitizedDataPoints, + ) : unresolvedDataBreaches, }, )} diff --git a/src/emails/functions/reduceSanitizedDataPoints.ts b/src/emails/functions/reduceSanitizedDataPoints.ts new file mode 100644 index 0000000000..3d66af67a2 --- /dev/null +++ b/src/emails/functions/reduceSanitizedDataPoints.ts @@ -0,0 +1,13 @@ +/* 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/. */ + +// data is inteferred from the SanitizedDataPoints type, but we ran into inference issues directly importing it +export function sumSanitizedDataPoints( + data: Array>, +): number { + return data.reduce((accumulatedValue, currentDataPoint) => { + const dataPointValue = Object.values(currentDataPoint)[0]; + return accumulatedValue + dataPointValue; + }, 0); +} diff --git a/src/emails/templates/monthlyActivityFree/MonthlyActivityFreeEmail.tsx b/src/emails/templates/monthlyActivityFree/MonthlyActivityFreeEmail.tsx index 5673b1c3b4..1199629f53 100644 --- a/src/emails/templates/monthlyActivityFree/MonthlyActivityFreeEmail.tsx +++ b/src/emails/templates/monthlyActivityFree/MonthlyActivityFreeEmail.tsx @@ -13,6 +13,7 @@ import { isEligibleForPremium } from "../../../app/functions/universal/premium"; import { getSignupLocaleCountry } from "../../functions/getSignupLocaleCountry"; import { HeaderStyles, MetaTags } from "../HeaderStyles"; import { SanitizedSubscriberRow } from "../../../app/functions/server/sanitize"; +import { sumSanitizedDataPoints } from "../../functions/reduceSanitizedDataPoints"; export type MonthlyActivityFreeEmailProps = { l10n: ExtendedReactLocalization; @@ -78,19 +79,15 @@ export const MonthlyActivityFreeEmail = ( // Show a sum of resolved data breach & broker exposures if a scan has been run // Otherwise, only show resolved data breaches dataPointValue: hasRunFreeScan - ? props.dataSummary.fixedSanitizedDataPoints.reduce( - (total, dataPointSummary) => { - return total + Object.values(dataPointSummary)[0]; - }, - 0, - ) + ? sumSanitizedDataPoints(props.dataSummary.fixedSanitizedDataPoints) : props.dataSummary.dataBreachResolvedNum, // The resolved box would be active if // a user has run a free scan and they have resolved data breaches, and or brokers (count number of resolved data points) // if a user hasn't run a free scan but they have resolved data breaches (count number of resolved breach cards) activeState: (hasRunFreeScan && - props.dataSummary.fixedSanitizedDataPoints.length > 0) || + sumSanitizedDataPoints(props.dataSummary.fixedSanitizedDataPoints) > + 0) || (!hasRunFreeScan && props.dataSummary.dataBreachResolvedNum > 0), }; @@ -101,7 +98,9 @@ export const MonthlyActivityFreeEmail = ( preScan: !hasRunFreeScan && props.dataSummary.dataBreachUnresolvedNum === 0, postScan: hasRunFreeScan && - props.dataSummary.unresolvedSanitizedDataPoints.length === 0, + sumSanitizedDataPoints( + props.dataSummary.unresolvedSanitizedDataPoints, + ) === 0, }; return ( From 11a8025e450a22ee07a2220c14ac65abff10e87d Mon Sep 17 00:00:00 2001 From: mozilla-pontoon Date: Tue, 29 Oct 2024 12:02:06 +0000 Subject: [PATCH 02/11] Import translations from l10n repository (2024-10-29) --- locales/el/app.ftl | 2 +- locales/el/breaches.ftl | 2 +- locales/gn/app.ftl | 2 +- locales/gn/breaches.ftl | 14 ++++---------- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/locales/el/app.ftl b/locales/el/app.ftl index aeb98c87e1..b2a0b25b1a 100644 --- a/locales/el/app.ftl +++ b/locales/el/app.ftl @@ -250,7 +250,7 @@ error-page-error-other-title = { $errorCode }: Κάτι πήγε στραβά ## Breach overview page -all-breaches-headline-3 = Βάση δεδομένων παραβίασης δεδομένων +all-breaches-headline-3 = Βάση δεδομένων παραβιάσεων all-breaches-lead = Παρακολουθούμε όλες τις γνωστές παραβιάσεις δεδομένων για να διαπιστώσουμε εάν τα προσωπικά σας στοιχεία παραβιάστηκαν. Ακολουθεί μια πλήρης λίστα με όλες τις παραβιάσεις που έχουν αναφερθεί από το 2007. search-breaches = Αναζήτηση παραβιάσεων # the kind of user data exposed to hackers in data breach. diff --git a/locales/el/breaches.ftl b/locales/el/breaches.ftl index 268f022c36..0bc4e59bff 100644 --- a/locales/el/breaches.ftl +++ b/locales/el/breaches.ftl @@ -2,7 +2,7 @@ # 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/. -breach-all-meta-page-title = Βάση δεδομένων παραβίασης δεδομένων — { -brand-fx-monitor } +breach-all-meta-page-title = Βάση δεδομένων παραβιάσεων — { -brand-fx-monitor } breach-all-meta-social-title = Όλες οι παραβιάσεις που εντοπίστηκαν από το { -brand-fx-monitor } breach-all-meta-social-description = Περιηγηθείτε στην πλήρη λίστα των γνωστών παραβιάσεων που εντοπίστηκαν από το { -brand-fx-monitor } και μάθετε εάν αποκαλύφθηκαν οι πληροφορίες σας. # Variables: diff --git a/locales/gn/app.ftl b/locales/gn/app.ftl index e4503a10c4..cb0e1bff77 100644 --- a/locales/gn/app.ftl +++ b/locales/gn/app.ftl @@ -209,7 +209,7 @@ error-page-error-other-title = { $errorCode } Oĩ osẽvaíva ## Breach overview page -all-breaches-headline-2 = Opaite ñembogua ohecháva { -brand-fx-monitor } +all-breaches-headline-3 = Mba’ekuaarã rupa ñembogua rehegua all-breaches-lead = Rohechapaite umi mba’ekuaarã ñembogua roikuaa hag̃ua ne maranduetépa oñembyaikuaára’e. Ko’ápe oĩ peteĩ tysýi opaite ñembogua oñemomaranduva’ekue rehegua ary 2007 guive. search-breaches = Ñembyai jeheka # the kind of user data exposed to hackers in data breach. diff --git a/locales/gn/breaches.ftl b/locales/gn/breaches.ftl index ea2e2b7fb2..93a71b1954 100644 --- a/locales/gn/breaches.ftl +++ b/locales/gn/breaches.ftl @@ -2,21 +2,17 @@ # 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/. -breach-all-meta-title = { -brand-fx-monitor } - Opaite mba’ekuaarã ñembogua +breach-all-meta-page-title = Mba’ekuaarã rupa ñembogua rehegua — { -brand-fx-monitor } breach-all-meta-social-title = Opaite ñembogua ohecháva { -brand-fx-monitor } breach-all-meta-social-description = Ehapykueho { -brand-fx-monitor } ñemboguakuaa rysýi ojehechakuaáva, upéi ehecha ne marandúpa oñemboguakuaápara’e. - +# Variables: +# $company (String) - Name of the company that was breached, e.g. "PHP Freaks" +breach-detail-meta-page-title = Mba’ekuaarã rupa ñembogua { $company } – { -brand-fx-monitor } rehegua # Variables: # $company (String) - Name of the company that was breached, e.g. "PHP Freaks" breach-detail-meta-social-title = ¿Ne myangekói { $company } mba’ekuaarã ñembogua? breach-detail-meta-social-description = Eiporu { -brand-fx-monitor } eikuaa hag̃ua ne maranduetépa oñemboguakuaára’e ha péicha rupi eikuaa mba’etépa ejapóta. -## Breaches header - -## Breaches resolved filter - -## Breaches table - ## Links that we might refer to when prompting the user to make changes after a breach breach-checklist-link-firefox-relay = { -brand-relay } @@ -26,7 +22,6 @@ breach-checklist-link-mozilla-vpn = { -brand-mozilla-vpn } ## Prompts the user for changes when there is a breach detected of password breach-checklist-pw-header-text = Embohekopyahu ñe’ẽñemi ha embojuruja mokõi papapyñemi (2FA). - # The `breached-company-link` tags will be replaced with link tags or stripped if no link is available. # Variables: # $passwordManagerLink (string) - a link to the password manager documentation, with { -breach-checklist-link-password-manager } as the label @@ -95,7 +90,6 @@ breach-checklist-phone-header-2 = Emo’ã ne pumbyry papapy mba’eporu rovamo ## Prompts the user for changes when there is a breach detected of security questions breach-checklist-sq-header-text = Embopyahu porandu tekorosãgua. - # The `breached-company-link` tags will be replaced with link tags or stripped if no link is available. breach-checklist-sq-body-text = Hetavejey, ro’e ndéve embohekopyahu hag̃ua porandu tekorosãgua mba’apohaguasu ñanduti rendápe. Hákatu iñanduti renda hekopytakuaa térã oreko tetepy ivaikuaáva, upévare ema’ẽke rendápe jeike. Eñemo’ãve hag̃ua, embohekopyahu ko’ã porandu tekorosãgua oimeraẽva mba’ete eiporuvéva peteĩva hendápe, ha emoheñói ñe’ẽñemi oiko ha iñambuéva peteĩteĩva ñe’ẽñemíme. From 2ffbf2aabc0f6f35265af87dc4edac058973fd61 Mon Sep 17 00:00:00 2001 From: Kaitlyn Andres Date: Tue, 29 Oct 2024 10:23:28 -0400 Subject: [PATCH 03/11] MNTOR-3640 - Use light mode only (#5255) * enforce white text on darkmode * add new logo * add new monitor logo * move styling out of prefers color scheme light * revert padding * pair bg with color * universal selector * use light only --- src/emails/templates/EmailFooter.tsx | 37 ++++--------------------- src/emails/templates/HeaderStyles.tsx | 39 +++++++++++---------------- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/src/emails/templates/EmailFooter.tsx b/src/emails/templates/EmailFooter.tsx index 15a1057d88..05dfbd30bb 100644 --- a/src/emails/templates/EmailFooter.tsx +++ b/src/emails/templates/EmailFooter.tsx @@ -31,12 +31,7 @@ export const EmailFooter = (props: Props) => { height="36px" align="center" /> - + {l10n.getString("email-footer-support-heading")} @@ -76,12 +71,7 @@ export const EmailFooter = (props: Props) => { }, )} - + {l10n.getFragment("email-footer-source-hibp", { elems: { "hibp-link": ( @@ -100,20 +90,10 @@ export const EmailFooter = (props: Props) => { width="150px" align="center" /> - + 149 New Montgomery St, 4th Floor, San Francisco, CA 94105 - + {l10n.getString("terms-of-service")} @@ -151,12 +131,7 @@ export const RedesignedEmailFooter = (props: Props) => { height="36px" align="center" /> - + {l10n.getString("email-footer-support-heading")} @@ -183,7 +158,6 @@ export const RedesignedEmailFooter = (props: Props) => { align="center" /> { San Francisco, CA 94105 { return ( - - + + ); }; @@ -15,31 +15,24 @@ export const MetaTags = () => { export const HeaderStyles = () => { const hideBgImageOnDarkMode = ` :root { - color-scheme: light dark; - supported-color-schemes: light dark; + color-scheme: light only; + supported-color-schemes: light only; } - @media (prefers-color-scheme: light) { - .footer_background { - background-image: url(${process.env.SERVER_URL}/images/email/footer-bg-shapes.png); - background-position: center bottom; - background-repeat: no-repeat; - } + .footer_background { + background-image: url(${process.env.SERVER_URL}/images/email/footer-bg-shapes.png); + background-position: center bottom; + background-repeat: no-repeat; + color: #000000; + background: #fffff; } - @media (prefers-color-scheme: light) { - .hero_background { - background-image: url(${process.env.SERVER_URL}/images/email/hero-bg-gradient.png); - background-repeat: repeat; - background-color: #e4d2ff; - background-position-x: 0; - } - } - - @media (prefers-color-scheme: dark) { - .hero_background { - background: none !important; - } + .hero_background { + background-image: url(${process.env.SERVER_URL}/images/email/hero-bg-gradient.png); + background-repeat: repeat; + background-position-x: 0; + background-color: #e4d2ff; + color: #ffffff; } `; From 824d5e946091d53b29698fb1967fc78a65205604 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Tue, 29 Oct 2024 16:12:58 +0100 Subject: [PATCH 04/11] feat: Add admin view for attached clients --- .../admin/fxa/AttachedClients.tsx | 29 ++++++++++ .../(authenticated)/admin/fxa/actions.tsx | 36 ++++++++++++ .../(authenticated)/admin/fxa/page.tsx | 22 ++++++++ src/utils/fxa.ts | 56 +++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/AttachedClients.tsx create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/actions.tsx create mode 100644 src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/page.tsx diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/AttachedClients.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/AttachedClients.tsx new file mode 100644 index 0000000000..ac01bf3787 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/AttachedClients.tsx @@ -0,0 +1,29 @@ +/* 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 { useEffect, useState } from "react"; +import { getAttachedClientsAction } from "./actions"; +import { FxaGetAccountAttachedClients } from "../../../../../../utils/fxa"; + +export const AttachedClients = () => { + const [data, setData] = useState(); + + useEffect(() => { + getAttachedClientsAction() + .then((attachedClients) => { + setData(attachedClients); + }) + .catch((error) => { + console.error("Could not get attached clients", error); + }); + }, []); + + return data ? ( +
{JSON.stringify(data, null, 2)}
+ ) : ( + "No data available" + ); +}; diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/actions.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/actions.tsx new file mode 100644 index 0000000000..afe06de11a --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/actions.tsx @@ -0,0 +1,36 @@ +/* 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 server"; + +import { notFound } from "next/navigation"; +import { getAttachedClients } from "../../../../../../utils/fxa"; +import { getServerSession } from "../../../../../functions/server/getServerSession"; +import { isAdmin } from "../../../../../api/utils/auth"; +import { logger } from "@sentry/utils"; +import { captureException } from "@sentry/node"; + +export async function getAttachedClientsAction() { + const session = await getServerSession(); + + if ( + !session?.user?.email || + !isAdmin(session.user.email) || + process.env.APP_ENV === "production" + ) { + return notFound(); + } + + try { + const attachedClients = await getAttachedClients( + session?.user.subscriber?.fxa_access_token ?? "", + ); + return attachedClients; + } catch (error) { + captureException(error); + logger.error("Could not get attached clients", { + error: JSON.stringify(error), + }); + } +} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/page.tsx new file mode 100644 index 0000000000..62f362f157 --- /dev/null +++ b/src/app/(proper_react)/(redesign)/(authenticated)/admin/fxa/page.tsx @@ -0,0 +1,22 @@ +/* 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 { getServerSession } from "../../../../../functions/server/getServerSession"; +import { notFound } from "next/navigation"; +import { isAdmin } from "../../../../../api/utils/auth"; +import { AttachedClients } from "./AttachedClients"; + +export default async function DevPage() { + const session = await getServerSession(); + + if ( + !session?.user?.email || + !isAdmin(session.user.email) || + process.env.APP_ENV === "productions" + ) { + return notFound(); + } + + return ; +} diff --git a/src/utils/fxa.ts b/src/utils/fxa.ts index f55d041e9d..8477a127f5 100644 --- a/src/utils/fxa.ts +++ b/src/utils/fxa.ts @@ -349,6 +349,61 @@ async function applyCoupon( } /* c8 ignore stop */ +/** + * @see https://mozilla.github.io/ecosystem-platform/api#tag/Devices-and-Sessions/operation/getAccountAttached_clients + */ +export type FxaGetAccountAttachedClients = { + clientId: string; + deviceId: number; + sessionTokenId: string; + refreshTokenId: string; + isCurrentSession: boolean; + deviceType: string; + name: string; + createdTime: string; + lastAccessTime: string; + scope: string[]; + userAgent: string; + createdTimeFormatted?: string; + approximateLastAccessTime?: number; + location?: { + city: string; + country: string; + state: string; + stateCode: string; + }; + os?: string; +}; + +// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy +/* c8 ignore start */ +async function getAttachedClients( + bearerToken: string, +): Promise { + const endpointUrl = `${envVars.OAUTH_ACCOUNT_URI}/account/attached_clients`; + try { + const response = await fetch(endpointUrl, { + headers: { + Accept: "application/json", + Authorization: `Bearer ${bearerToken}`, + }, + }); + const responseJson = await response.json(); + if (!response.ok) throw new Error(responseJson); + logger.info("get_fxa_attached_clients_success"); + return responseJson as FxaGetAccountAttachedClients[]; + } catch (e) { + if (e instanceof Error) { + logger.error("get_fxa_attached_clients", { + stack: e.stack, + message: e.message, + }); + } + throw e; + } +} +/* c8 ignore stop */ + // TODO: Add unit test when changing this code: /* c8 ignore next 3 */ function getSha1(email: crypto.BinaryLike) { @@ -364,4 +419,5 @@ export { getBillingAndSubscriptions, deleteSubscription, applyCoupon, + getAttachedClients, }; From db83e13fdc226596dc2646b2c363c27f78557ca3 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Tue, 29 Oct 2024 17:07:39 +0100 Subject: [PATCH 05/11] fix: Stringify attached client response for error logging --- src/utils/fxa.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/fxa.ts b/src/utils/fxa.ts index 8477a127f5..a7ea81b1a8 100644 --- a/src/utils/fxa.ts +++ b/src/utils/fxa.ts @@ -389,7 +389,7 @@ async function getAttachedClients( }, }); const responseJson = await response.json(); - if (!response.ok) throw new Error(responseJson); + if (!response.ok) throw new Error(JSON.stringify(responseJson)); logger.info("get_fxa_attached_clients_success"); return responseJson as FxaGetAccountAttachedClients[]; } catch (e) { From 916a3c90b9c9a83897b708c3d3983f1c439d5938 Mon Sep 17 00:00:00 2001 From: Kaitlyn Andres Date: Tue, 29 Oct 2024 14:08:08 -0400 Subject: [PATCH 06/11] Enforce dark text on footer and header (#5263) --- src/emails/templates/HeaderStyles.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/emails/templates/HeaderStyles.tsx b/src/emails/templates/HeaderStyles.tsx index ca7c93d8b8..d225e0c7cf 100644 --- a/src/emails/templates/HeaderStyles.tsx +++ b/src/emails/templates/HeaderStyles.tsx @@ -23,16 +23,14 @@ export const HeaderStyles = () => { background-image: url(${process.env.SERVER_URL}/images/email/footer-bg-shapes.png); background-position: center bottom; background-repeat: no-repeat; - color: #000000; - background: #fffff; + color: #000000 !important; } .hero_background { background-image: url(${process.env.SERVER_URL}/images/email/hero-bg-gradient.png); background-repeat: repeat; background-position-x: 0; - background-color: #e4d2ff; - color: #ffffff; + color: #000000 !important; } `; From 7800c01b79788de3fe386690105c0f7c6abdcea9 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 30 Oct 2024 13:38:30 +0100 Subject: [PATCH 07/11] fix: Do not show the estimated removal times label and CSAT banner to free users --- .../(dashboard)/dashboard/Dashboard.test.tsx | 32 ++++++++++++++++--- .../client/ExposuresFilter.test.tsx | 6 ++-- src/app/components/client/ExposuresFilter.tsx | 5 ++- .../surveys/removalTimeEstimates.ts | 5 --- .../exposure_card/ExposureCard.test.tsx | 23 ++++++++++--- .../client/exposure_card/ScanResultCard.tsx | 7 ++-- .../exposure_card/SubscriberBreachCard.tsx | 8 +++-- 7 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/Dashboard.test.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/Dashboard.test.tsx index a561343834..2bc43a344b 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/Dashboard.test.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/Dashboard.test.tsx @@ -3645,7 +3645,7 @@ describe("CSAT survey banner", () => { expect(answerButton).not.toBeInTheDocument(); }); - it("displays the removal time estimates CSAT survey on the “fixed” tab for users on the treatment branch", () => { + it("displays the removal time estimates CSAT survey on the “fixed” tab to Plus users on the treatment branch", () => { const ComposedDashboard = composeStory( DashboardUsPremiumResolvedScanNoBreaches, Meta, @@ -3669,7 +3669,7 @@ describe("CSAT survey banner", () => { expect(answerButton).toBeInTheDocument(); }); - it("displays the removal time estimates CSAT survey on the “fixed” tab for users on the control branch", () => { + it("displays the removal time estimates CSAT survey on the “fixed” tab to Plus users on the control branch", () => { const ComposedDashboard = composeStory( DashboardUsPremiumResolvedScanNoBreaches, Meta, @@ -3693,7 +3693,7 @@ describe("CSAT survey banner", () => { expect(answerButton).toBeInTheDocument(); }); - it("does not display the removal time estimates CSAT survey on the ”action needed” tab for users on the treatment branch", () => { + it("does not display the removal time estimates CSAT survey on the “action needed” tab to Plus users on the treatment branch", () => { const ComposedDashboard = composeStory( DashboardUsPremiumResolvedScanNoBreaches, Meta, @@ -3705,7 +3705,31 @@ describe("CSAT survey banner", () => { experimentData={{ ...defaultExperimentData, "data-broker-removal-time-estimates": { - enabled: false, + enabled: true, + }, + }} + />, + ); + + const answerButton = screen.queryByRole("button", { + name: "Neutral", + }); + expect(answerButton).not.toBeInTheDocument(); + }); + + it("does not display the removal time estimates CSAT survey on the “fixed” tab to free users on the treatment branch", () => { + const ComposedDashboard = composeStory( + DashboardUsNoPremiumUnresolvedScanNoBreaches, + Meta, + ); + render( + , diff --git a/src/app/components/client/ExposuresFilter.test.tsx b/src/app/components/client/ExposuresFilter.test.tsx index 7960396c36..279cfbed21 100644 --- a/src/app/components/client/ExposuresFilter.test.tsx +++ b/src/app/components/client/ExposuresFilter.test.tsx @@ -45,11 +45,12 @@ it("shows and hides the exposure type explainer", async () => { expect(explainerDialog).not.toBeInTheDocument(); }); -it("shows and hides the removal time explainer dialog by clicking the “Got it” button", async () => { +it("shows and hides the removal time explainer dialog by clicking the “Got it” button to Plus subscribers", async () => { const user = userEvent.setup(); const ExposuresFilter = composeStory(ExposuresFilterDefault, Meta); render( { +it("shows and hides the removal time explainer dialog by clicking the close button to Plus subscribers", async () => { const user = userEvent.setup(); const ExposuresFilter = composeStory(ExposuresFilterDefault, Meta); render( {l10n.getString("dashboard-exposures-filter-date-found")} - {enabledFeatureFlags.includes("DataBrokerRemovalTimeEstimateLabel") && + {isPlusSubscriber && + enabledFeatureFlags.includes( + "DataBrokerRemovalTimeEstimateLabel", + ) && experimentData["data-broker-removal-time-estimates"].enabled && (
  • {l10n.getString( diff --git a/src/app/components/client/csat_survey/surveys/removalTimeEstimates.ts b/src/app/components/client/csat_survey/surveys/removalTimeEstimates.ts index a07091331c..f3a6562fef 100644 --- a/src/app/components/client/csat_survey/surveys/removalTimeEstimates.ts +++ b/src/app/components/client/csat_survey/surveys/removalTimeEstimates.ts @@ -18,11 +18,6 @@ const surveyData: SurveyData = { }, ], variations: [ - { - id: "free-user", - showForUser: ["free-user"], - showOnTab: ["fixed"], - }, { id: "plus-user", showForUser: ["plus-user"], diff --git a/src/app/components/client/exposure_card/ExposureCard.test.tsx b/src/app/components/client/exposure_card/ExposureCard.test.tsx index 7f8e801747..9d1bfbf8fa 100644 --- a/src/app/components/client/exposure_card/ExposureCard.test.tsx +++ b/src/app/components/client/exposure_card/ExposureCard.test.tsx @@ -148,7 +148,19 @@ describe("DataBreachCard", () => { it("does not show the estimated removal time if the feature flag `DataBrokerRemovalTimeEstimates` is disabled", () => { const ComposedExposureCard = composeStory(DataBrokerActionNeeded, Meta); - render(); + render(); + + const removalTimeTitle = screen.queryByText("Removal time"); + expect(removalTimeTitle).not.toBeInTheDocument(); + }); + + it("does not show the estimated removal time for free users", () => { + const ComposedExposureCard = composeStory(DataBrokerActionNeeded, Meta); + render( + , + ); const removalTimeTitle = screen.queryByText("Removal time"); expect(removalTimeTitle).not.toBeInTheDocument(); @@ -168,11 +180,12 @@ describe("DataBreachCard", () => { label: "181+ days", }, ])( - "shows a label with the estimated removal time if available: %s", + "shows a label with the estimated removal time if available to Plus subscribers: %s", ({ removalTime, label }) => { const ComposedExposureCard = composeStory(DataBrokerActionNeeded, Meta); render( , @@ -185,10 +198,11 @@ describe("DataBreachCard", () => { }, ); - it("shows a label displaying “unknown” if the removal time is not available", () => { + it("shows a label displaying “unknown” if the removal time is not available to Plus subscribers", () => { const ComposedExposureCard = composeStory(DataBrokerActionNeeded, Meta); render( , ); @@ -199,10 +213,11 @@ describe("DataBreachCard", () => { expect(removalTimeLabel).toBeInTheDocument(); }); - it("shows a label displaying “N/A” on data breach cards", () => { + it("shows a label displaying “N/A” on data breach cards to Plus subscribers", () => { const ComposedExposureCard = composeStory(DataBreachActionNeeded, Meta); render( , ); diff --git a/src/app/components/client/exposure_card/ScanResultCard.tsx b/src/app/components/client/exposure_card/ScanResultCard.tsx index 3f0196b4a5..890fb70520 100644 --- a/src/app/components/client/exposure_card/ScanResultCard.tsx +++ b/src/app/components/client/exposure_card/ScanResultCard.tsx @@ -266,9 +266,10 @@ export const ScanResultCard = (props: ScanResultCardProps) => {
    {dateFormatter.format(scanResult.created_at)}
    - {props.enabledFeatureFlags?.includes( - "DataBrokerRemovalTimeEstimateLabel", - ) && + {props.isPremiumUser && + props.enabledFeatureFlags?.includes( + "DataBrokerRemovalTimeEstimateLabel", + ) && props.experimentData?.["data-broker-removal-time-estimates"] .enabled && ( <> diff --git a/src/app/components/client/exposure_card/SubscriberBreachCard.tsx b/src/app/components/client/exposure_card/SubscriberBreachCard.tsx index 1ace18d83e..8778a3a1f5 100644 --- a/src/app/components/client/exposure_card/SubscriberBreachCard.tsx +++ b/src/app/components/client/exposure_card/SubscriberBreachCard.tsx @@ -34,6 +34,7 @@ export type SubscriberBreachCardProps = { locale: string; resolutionCta: ReactNode; isEligibleForPremium: boolean; + isPremiumUser: boolean; isExpanded: boolean; enabledFeatureFlags: FeatureFlagName[]; experimentData: ExperimentData; @@ -211,9 +212,10 @@ export const SubscriberBreachCard = (props: SubscriberBreachCardProps) => {
    {dateFormatter.format(subscriberBreach.addedDate)}
    - {props.enabledFeatureFlags.includes( - "DataBrokerRemovalTimeEstimateLabel", - ) && + {props.isPremiumUser && + props.enabledFeatureFlags.includes( + "DataBrokerRemovalTimeEstimateLabel", + ) && props.experimentData["data-broker-removal-time-estimates"] .enabled && ( <> From fd29cd2a56ebcda7631f59976919d9d4c3ff7a1d Mon Sep 17 00:00:00 2001 From: mozilla-pontoon Date: Wed, 30 Oct 2024 12:01:38 +0000 Subject: [PATCH 08/11] Import translations from l10n repository (2024-10-30) --- locales/vi/breaches.ftl | 2 +- locales/vi/fix.ftl | 3 +-- locales/vi/recommendations.ftl | 22 +--------------------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/locales/vi/breaches.ftl b/locales/vi/breaches.ftl index 8cf89cfc38..d484508c2d 100644 --- a/locales/vi/breaches.ftl +++ b/locales/vi/breaches.ftl @@ -31,7 +31,7 @@ breach-checklist-pw-body-text = Trong hầu hết các trường hợp, chúng t # Variables: # $firefoxRelayLink (string) - a link to Firefox Relay, with { -breach-checklist-link-firefox-relay } as the label -breach-checklist-email-header-2 = Bảo vệ email của bạn bằng dịch vụ tạo mặt nạ email như { $firefoxRelayLink }. +breach-checklist-email-header-2 = Bảo vệ email của bạn bằng dịch vụ tạo email ẩn danh như { $firefoxRelayLink }. breach-checklist-email-body = Điều này có thể ẩn địa chỉ email thực của bạn trong khi chuyển tiếp email đến hộp thư đến thực của bạn. ## Prompts the user for changes when there is a breach detected of social security number diff --git a/locales/vi/fix.ftl b/locales/vi/fix.ftl index 8dd7db7504..9b4620620d 100644 --- a/locales/vi/fix.ftl +++ b/locales/vi/fix.ftl @@ -5,7 +5,6 @@ fix-flow-nav-high-risk-data-breaches = Vụ rò rỉ với rủi ro cao fix-flow-nav-leaked-passwords = Mật khẩu bị lộ fix-flow-nav-security-recommendations = Đề xuất bảo mật - guided-resolution-flow-exit = Quay lại trang tổng quan guided-resolution-flow-next-arrow = Chuyển sang bước tiếp theo guided-resolution-flow-step-navigation-label = Các bước hướng dẫn @@ -142,7 +141,7 @@ security-recommendation-email-description = Thật không may, bạn không th security-recommendation-email-step-one = Đừng nhấp vào liên kết trong email từ những người gửi không xác định; nếu nó có vẻ đến từ nguồn đáng tin cậy, hãy gọi trực tiếp để xác nhận security-recommendation-email-step-two = Hãy cẩn thận với lừa đảo giả mạo security-recommendation-email-step-three = Đánh dấu các email đáng ngờ là thư rác và chặn người gửi -security-recommendation-email-step-four = Sử dụng mặt nạ email của { -brand-relay } để bảo vệ email của bạn trong tương lai +security-recommendation-email-step-four = Sử dụng email ẩn danh của { -brand-relay } để bảo vệ email của bạn trong tương lai # IP security recommendation diff --git a/locales/vi/recommendations.ftl b/locales/vi/recommendations.ftl index 4125a0e4f7..41a5bf1ece 100644 --- a/locales/vi/recommendations.ftl +++ b/locales/vi/recommendations.ftl @@ -1,4 +1,3 @@ - # 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/. @@ -11,7 +10,6 @@ rec-ssn = Bạn nhận được ba báo cáo tín dụng miễn phí một năm theo luật. Yêu cầu và xem xét chúng không ảnh hưởng đến tín dụng của bạn. Tìm kiếm các tài khoản, khoản vay hoặc thẻ tín dụng mà bạn không nhận ra. - # Recommendation subhead rec-pw-1-subhead = Thay đổi mật khẩu của bạn # Link title @@ -20,7 +18,6 @@ rec-pw-1-2 = Đặt mật khẩu này là duy nhất và khác với bất kỳ mật khẩu nào khác mà bạn sử dụng. Một chiến lược tốt để làm theo là kết hợp hai hoặc nhiều từ để tạo cụm mật khẩu và bao gồm các số và ký tự đặc biệt. - # Recommendation subhead rec-pw-2-subhead = Cập nhật thông tin đăng nhập khác bằng cùng một mật khẩu # Link title @@ -28,7 +25,6 @@ rec-pw-2-cta-fx = Xem thông tin đăng nhập trong { -brand-name } rec-pw-2 = Sử dụng lại mật khẩu biến một rò rỉ dữ liệu thành nhiều hơn. Bây giờ mật khẩu này đã được sử dụng, tin tặc có thể sử dụng nó để vào các tài khoản khác. - # Recommendation subhead rec-pw-3-subhead = Sử dụng trình quản lý mật khẩu để lấy mật khẩu của bạn ở mọi nơi # Link title @@ -39,7 +35,6 @@ rec-pw-3-fx = rec-pw-3-non-fx = Sử dụng { -brand-lockwise } để theo dõi tất cả các mật khẩu khác nhau của bạn và truy cập chúng một cách an toàn từ điện thoại hoặc máy tính bảng của bạn. - # Recommendation subhead rec-pw-4-subhead = Thiết lập xác thực hai yếu tố (2FA) # Link title @@ -48,76 +43,64 @@ rec-pw-4 = Nhiều trang web cung cấp 2FA như một biện pháp bảo mật bổ sung. Điều này đòi hỏi một thông tin khác để đăng nhập vào tài khoản của bạn, chẳng hạn như mã một lần bạn nhận được qua văn bản. - # Recommendation subhead rec-bank-acc-subhead = Theo dõi báo cáo ngân hàng của bạn rec-bank-acc = Kiểm tra báo cáo ngân hàng của bạn cho hoạt động đáng ngờ hoặc chi phí bất thường. Thông báo cho ngân hàng của bạn nếu bạn thấy bất cứ điều gì bạn không nhận ra - # Recommendation subhead rec-cc-subhead = Theo dõi sao kê thẻ tín dụng của bạn rec-cc = Xem ra các khoản phí lạ trên thẻ tín dụng của bạn. Bạn có thể muốn yêu cầu một thẻ mới có số mới từ nhà phát hành thẻ tín dụng của bạn. - # Recommendation subhead -rec-email-mask-subhead = Sử dụng mặt nạ email +rec-email-mask-subhead = Sử dụng email ẩn danh rec-email-cta = Thử { -brand-relay } rec-email = Cung cấp địa chỉ email thực của bạn giúp tin tặc hoặc kẻ theo dõi tìm mật khẩu của bạn dễ dàng hơn hoặc nhắm mục tiêu bạn trực tuyến. Một dịch vụ như { -brand-relay } ẩn địa chỉ email thực của bạn trong khi chuyển tiếp email đến hộp thư đến thực của bạn. - # Recommendation subhead rec-ip-subhead-2 = Sử dụng VPN để giấu địa chỉ IP của bạn - # Recommendation subhead rec-moz-vpn-cta = Hãy thử { -brand-mozilla-vpn } rec-moz-vpn-update-2 = Địa chỉ giao thức Internet (địa chỉ IP) xác định chính xác vị trí của bạn và nhà cung cấp dịch vụ internet. Một dịch vụ như { -brand-mozilla-vpn } có thể giấu địa chỉ IP của bạn để ẩn vị trí của bạn. - rec-hist-pw-subhead = Tránh sử dụng lại mật khẩu # Link title rec-hist-pw-cta-fx = Xem thông tin đăng nhập trong { -brand-name } rec-hist-pw = Sử dụng mật khẩu duy nhất, mạnh mẽ cho mọi tài khoản. Nếu một mật khẩu bị lộ trong một vụ rò rỉ dữ liệu, bạn sẽ chỉ cần cập nhật một lần đăng nhập đó. - # Recommendation subhead rec-sec-qa-subhead = Tạo câu trả lời độc đáo cho câu hỏi bảo mật rec-sec-qa = Nhiều trang web đặt câu hỏi tương tự. Nếu một câu trả lời bị lộ, thông tin sẽ bị lộ. Tạo câu trả lời dài, ngẫu nhiên và lưu trữ chúng ở nơi an toàn. - # Recommendation subhead rec-phone-num-subhead = Tránh chia sẻ số điện thoại của bạn rec-phone-num = Cố gắng tránh đưa ra số điện thoại của bạn khi đăng ký mới tài khoản hoặc dịch vụ. Nếu không yêu cầu số điện thoại, thì không nên nhập vào. - # Recommendation subhead rec-dob-subhead = Tránh sử dụng thông tin cá nhân trong mã PIN rec-dob = Bởi vì ngày sinh của bạn rất dễ tìm thấy trong hồ sơ công cộng, tốt nhất là tránh sử dụng nó trong mật khẩu và mã PIN. Những người biết sinh nhật của bạn cũng có thể dễ dàng đoán mã PIN của bạn. - # Recommendation subhead rec-pins-subhead = Tăng cường bảo mật mã PIN của bạn rec-pins = Mã PIN mạnh không chứa thông tin cá nhân, chẳng hạn như ngày sinh của bạn hoặc địa chỉ. Nó chỉ là một con số mà bạn biết và không thể dễ dàng đoán được. - # Recommendation subhead rec-address-subhead = Tránh sử dụng địa chỉ trong mật khẩu rec-address = Sử dụng địa chỉ hoặc đường phố nơi bạn lớn lên làm suy yếu mật khẩu. Vì nó dễ dàng tìm thấy thông tin này một cách công khai, nó làm cho các mật khẩu này dễ đoán hơn. - # Recommendation subhead rec-gen-1-subhead = Sử dụng mật khẩu mạnh, duy nhất cho mọi tài khoản # Link title @@ -125,7 +108,6 @@ rec-gen-1-cta = Cách tạo mật khẩu mạnh rec-gen-1 = Sử dụng lại mật khẩu khiến tất cả các tài khoản của bạn có nguy cơ. Điều này có nghĩa là nếu một mật khẩu bị lộ, tin tặc có chìa khóa cho nhiều tài khoản. - # Recommendation subhead rec-gen-2-subhead = Lưu mật khẩu ở nơi an toàn # Link title @@ -133,7 +115,6 @@ rec-gen-2-cta = Những lầm tưởng về trình quản lý mật khẩu rec-gen-2 = Đặt chi tiết đăng nhập của bạn ở một nơi an toàn chỉ bạn mới có thể truy cập, chẳng hạn như quản lý mật khẩu. Điều này cũng giúp bạn dễ dàng theo dõi tất cả các mật khẩu khác nhau của mình. - # Recommendation subhead rec-gen-3-subhead = Hãy thận trọng về việc đưa ra thông tin cá nhân # Link title @@ -141,7 +122,6 @@ rec-gen-3-cta = Đọc thêm mẹo bảo mật rec-gen-3 = Đừng đưa ra dữ liệu cá nhân nếu bạn không cần phải làm vậy. Nếu bạn được yêu cầu nhập hoặc đưa ra địa chỉ email, mã ZIP hoặc số điện thoại, bạn có thể nói không. - # Recommendation subhead rec-gen-4-subhead = Cập nhật phần mềm và ứng dụng thường xuyên rec-gen-4 = From 6442d3ba6ff94b44c0182b6435c82f571b022118 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 29 Oct 2024 11:44:20 +0100 Subject: [PATCH 09/11] Upload Sentry source maps by default --- next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/next.config.js b/next.config.js index e5e421f054..1b4048e643 100644 --- a/next.config.js +++ b/next.config.js @@ -205,7 +205,7 @@ const sentryOptions = { hideSourceMaps: false, sourcemaps: { - disable: process.env.UPLOAD_SENTRY_SOURCEMAPS !== "true", + disable: process.env.UPLOAD_SENTRY_SOURCEMAPS === "false", }, }; From 737adacc34f9f7f7e269c35be8ea891e5b4f3216 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 29 Oct 2024 12:23:01 +0100 Subject: [PATCH 10/11] Close the SMTP connection pool when done --- src/scripts/cronjobs/emailBreachAlerts.tsx | 3 ++- .../cronjobs/firstDataBrokerRemovalFixed.tsx | 5 ++++- src/scripts/cronjobs/monthlyActivityFree.tsx | 4 +++- src/scripts/cronjobs/monthlyActivityPlus.tsx | 4 +++- src/utils/email.test.ts | 10 ++++++++++ src/utils/email.ts | 19 ++++++++++++++----- 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/scripts/cronjobs/emailBreachAlerts.tsx b/src/scripts/cronjobs/emailBreachAlerts.tsx index 7f7c7b6233..15b239a504 100644 --- a/src/scripts/cronjobs/emailBreachAlerts.tsx +++ b/src/scripts/cronjobs/emailBreachAlerts.tsx @@ -22,7 +22,7 @@ import { addEmailNotification, markEmailAsNotified, } from "../../db/tables/email_notifications"; -import { initEmail, sendEmail } from "../../utils/email"; +import { initEmail, sendEmail, closeEmailPool } from "../../utils/email"; import { getAddressesAndLanguageForEmail, @@ -427,6 +427,7 @@ if (process.env.NODE_ENV !== "test") { await knexSubscribers.destroy(); await knexEmailAddresses.destroy(); await knexHibp.destroy(); + closeEmailPool(); Sentry.captureCheckIn({ checkInId, monitorSlug: SENTRY_SLUG, diff --git a/src/scripts/cronjobs/firstDataBrokerRemovalFixed.tsx b/src/scripts/cronjobs/firstDataBrokerRemovalFixed.tsx index 96fbf30ca2..2971b13ce6 100644 --- a/src/scripts/cronjobs/firstDataBrokerRemovalFixed.tsx +++ b/src/scripts/cronjobs/firstDataBrokerRemovalFixed.tsx @@ -7,7 +7,7 @@ import { getPotentialSubscribersWaitingForFirstDataBrokerRemovalFixedEmail, markFirstDataBrokerRemovalFixedEmailAsJustSent, } from "../../db/tables/subscribers"; -import { initEmail, sendEmail } from "../../utils/email"; +import { initEmail, sendEmail, closeEmailPool } from "../../utils/email"; import { renderEmail } from "../../emails/renderEmail"; import { FirstDataBrokerRemovalFixed } from "../../emails/templates/firstDataBrokerRemovalFixed/FirstDataBrokerRemovalFixed"; import { getCronjobL10n } from "../../app/functions/l10n/cronjobs"; @@ -103,6 +103,9 @@ async function run() { ); }), ); + + closeEmailPool(); + console.log( `[${new Date(Date.now()).toISOString()}] Sent [${subscribersToEmailWithData.length}] first data broker removal fixed emails.`, ); diff --git a/src/scripts/cronjobs/monthlyActivityFree.tsx b/src/scripts/cronjobs/monthlyActivityFree.tsx index 64ab8a6f80..0ce196c83a 100644 --- a/src/scripts/cronjobs/monthlyActivityFree.tsx +++ b/src/scripts/cronjobs/monthlyActivityFree.tsx @@ -4,7 +4,7 @@ import { SubscriberRow } from "knex/types/tables"; import { getFreeSubscribersWaitingForMonthlyEmail } from "../../db/tables/subscribers"; -import { initEmail, sendEmail } from "../../utils/email"; +import { initEmail, sendEmail, closeEmailPool } from "../../utils/email"; import { renderEmail } from "../../emails/renderEmail"; import { MonthlyActivityFreeEmail } from "../../emails/templates/monthlyActivityFree/MonthlyActivityFreeEmail"; import { getCronjobL10n } from "../../app/functions/l10n/cronjobs"; @@ -43,6 +43,8 @@ async function run() { sendMonthlyActivityEmail(subscriber), ), ); + + closeEmailPool(); console.log( `[${new Date(Date.now()).toISOString()}] Sent [${subscribersToEmail.length}] monthly activity emails to free users.`, ); diff --git a/src/scripts/cronjobs/monthlyActivityPlus.tsx b/src/scripts/cronjobs/monthlyActivityPlus.tsx index 25d55a91c4..f05c4ef6bf 100644 --- a/src/scripts/cronjobs/monthlyActivityPlus.tsx +++ b/src/scripts/cronjobs/monthlyActivityPlus.tsx @@ -7,7 +7,7 @@ import { getPlusSubscribersWaitingForMonthlyEmail, markMonthlyActivityPlusEmailAsJustSent, } from "../../db/tables/subscribers"; -import { initEmail, sendEmail } from "../../utils/email"; +import { initEmail, sendEmail, closeEmailPool } from "../../utils/email"; import { renderEmail } from "../../emails/renderEmail"; import { MonthlyActivityPlusEmail } from "../../emails/templates/monthlyActivityPlus/MonthlyActivityPlusEmail"; import { getCronjobL10n } from "../../app/functions/l10n/cronjobs"; @@ -40,6 +40,8 @@ async function run() { return sendMonthlyActivityEmail(subscriber); }), ); + + closeEmailPool(); console.log( `[${new Date(Date.now()).toISOString()}] Sent [${subscribersToEmail.length}] monthly activity emails to Plus users.`, ); diff --git a/src/utils/email.test.ts b/src/utils/email.test.ts index 9389e8ee66..e635834fd2 100644 --- a/src/utils/email.test.ts +++ b/src/utils/email.test.ts @@ -154,6 +154,16 @@ test("EmailUtils.init with empty host uses jsonTransport. logs messages", async ); }); +test("EmailUtils.closeEmailPool before .init() fails", async () => { + expect.assertions(1); + + const { closeEmailPool } = await import("./email"); + + const expectedError = "`closeEmailPool` called before `initEmail`"; + + expect(() => closeEmailPool()).toThrow(expectedError); +}); + test("randomToken returns a random token of 2xlength (because of hex)", () => { const token = randomToken(32); expect(token).toHaveLength(64); diff --git a/src/utils/email.ts b/src/utils/email.ts index 151ec12d8b..e3a099f19f 100644 --- a/src/utils/email.ts +++ b/src/utils/email.ts @@ -15,7 +15,7 @@ let gTransporter: Transporter; const envVars = getEnvVarsOrThrow(["SMTP_URL", "EMAIL_FROM", "SES_CONFIG_SET"]); -async function initEmail(smtpUrl = envVars.SMTP_URL) { +export async function initEmail(smtpUrl = envVars.SMTP_URL) { // Allow a debug mode that will log JSON instead of sending emails. if (!smtpUrl) { logger.info("smtpUrl-empty", { @@ -30,6 +30,17 @@ async function initEmail(smtpUrl = envVars.SMTP_URL) { return gTransporterVerification; } +/** See https://nodemailer.com/smtp/pooled/ */ +export function closeEmailPool() { + if (!gTransporter) { + throw new Error("`closeEmailPool` called before `initEmail`"); + /* c8 ignore next 5 */ + } + // Not covered by tests because it involves a lot of mocks to basically check + // that we called this function. See test-coverage.md#mock-heavy + gTransporter.close(); +} + /** * Send Email * @@ -37,7 +48,7 @@ async function initEmail(smtpUrl = envVars.SMTP_URL) { * @param subject * @param html */ -async function sendEmail( +export async function sendEmail( recipient: string, subject: string, html: string, @@ -86,8 +97,6 @@ async function sendEmail( } } -function randomToken(length: number = 64) { +export function randomToken(length: number = 64) { return crypto.randomBytes(length).toString("hex"); } - -export { initEmail, sendEmail, randomToken }; From b2df6a2b0f9d37ed6598fac36a35ebbd6fab81ee Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 28 Oct 2024 16:32:02 +0100 Subject: [PATCH 11/11] Only initialise LoggingWinston when used The `gcpdev` env isn't really in use yet according to @rhelmer ("it's intended for GCP-based dev environments"), but at least locally for me, the `LoggingWinston` instantiation seems to contribute some slowdown to the termination of Node scripts, so this change ensures it's only initialised when actually used. It might not solve the issue we're seeing where the monthly-activity-free cron job is never terminating in stage, but even then at least it saves a few resources. --- src/app/functions/server/glean.ts | 15 ++++++++------- src/app/functions/server/logging.ts | 17 ++++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/app/functions/server/glean.ts b/src/app/functions/server/glean.ts index 44521ccf7d..8bfba10f8e 100644 --- a/src/app/functions/server/glean.ts +++ b/src/app/functions/server/glean.ts @@ -8,12 +8,13 @@ import { v4 as uuidv4 } from "uuid"; const GLEAN_EVENT_MOZLOG_TYPE = "glean-server-event"; -const loggingWinston = new LoggingWinston({ - labels: { - name: GLEAN_EVENT_MOZLOG_TYPE, - version: "0.1.0", - }, -}); +const getLoggingWinston = () => + new LoggingWinston({ + labels: { + name: GLEAN_EVENT_MOZLOG_TYPE, + version: "0.1.0", + }, + }); export function record( category: string, @@ -26,7 +27,7 @@ export function record( // In GCP environments, use cloud logging instead of stdout. // FIXME https://mozilla-hub.atlassian.net/browse/MNTOR-2401 - enable for stage and production transports: ["gcpdev"].includes(process.env.APP_ENV ?? "local") - ? [loggingWinston] + ? [getLoggingWinston()] : [new transports.Console()], }); diff --git a/src/app/functions/server/logging.ts b/src/app/functions/server/logging.ts index e3f544454e..b5e6bc4952 100644 --- a/src/app/functions/server/logging.ts +++ b/src/app/functions/server/logging.ts @@ -5,12 +5,15 @@ import { createLogger, transports } from "winston"; import { LoggingWinston } from "@google-cloud/logging-winston"; -const loggingWinston = new LoggingWinston({ - labels: { - name: "monitor-stats", - version: "0.1.0", - }, -}); +// Explicitly not run in tests (and other non-gcpdev environments) +/* c8 ignore next 7 */ +const getLoggingWinston = () => + new LoggingWinston({ + labels: { + name: "monitor-stats", + version: "0.1.0", + }, + }); export const logger = createLogger({ level: "info", @@ -18,6 +21,6 @@ export const logger = createLogger({ // FIXME https://mozilla-hub.atlassian.net/browse/MNTOR-2401 - enable for stage and production /* c8 ignore next 3 - cannot test this outside of GCP currently */ transports: ["gcpdev"].includes(process.env.APP_ENV ?? "local") - ? [loggingWinston] + ? [getLoggingWinston()] : [new transports.Console()], });