From fde294ec3371d8f30376ff9a5b533790f35051f4 Mon Sep 17 00:00:00 2001 From: Kaitlyn Date: Thu, 9 Jan 2025 10:40:19 -0500 Subject: [PATCH 1/5] fix user dashbaord state --- .../DashboardTopBannerContent.tsx | 5 ++- .../user/(dashboard)/dashboard/View.tsx | 3 ++ .../user/(dashboard)/dashboard/fix/page.tsx | 9 ++++- src/app/functions/server/dashboard.ts | 39 ++++++++++++------- .../server/getRelevantGuidedSteps.ts | 2 +- 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/DashboardTopBanner/DashboardTopBannerContent.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/DashboardTopBanner/DashboardTopBannerContent.tsx index b29bae2eca2..21b496606d0 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/DashboardTopBanner/DashboardTopBannerContent.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/DashboardTopBanner/DashboardTopBannerContent.tsx @@ -80,7 +80,10 @@ export const DashboardTopBannerContent = (props: DashboardTopBannerProps) => { ); } - const relevantGuidedStep = getNextGuidedStep(stepDeterminationData); + const relevantGuidedStep = getNextGuidedStep( + stepDeterminationData, + props.enabledFeatureFlags, + ); const contentProps = { relevantGuidedStep, diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/View.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/View.tsx index 5ec78b67381..c5e6ef9478c 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/View.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/View.tsx @@ -198,6 +198,7 @@ export const View = (props: Props) => { const removalTimeEstimate = isScanResult(exposure) ? props.removalTimeEstimates.find(({ d }) => d === exposure.data_broker) : undefined; + return (
  • { const dataSummary = getDashboardSummary( adjustedScanResults, props.userBreaches, + props.enabledFeatureFlags, ); const hasExposures = combinedArray.length > 0; @@ -513,6 +515,7 @@ export const View = (props: Props) => { bannerData={getDashboardSummary( adjustedScanResults, props.userBreaches, + props.enabledFeatureFlags, )} stepDeterminationData={{ countryCode, diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/page.tsx index c320f41bbf3..686a63091d5 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/page.tsx @@ -15,6 +15,7 @@ import { getScanResultsWithBroker } from "../../../../../../../../db/tables/oner import { getServerSession } from "../../../../../../../functions/server/getServerSession"; import { refreshStoredScanResults } from "../../../../../../../functions/server/refreshStoredScanResults"; import { hasPremium } from "../../../../../../../functions/universal/user"; +import { getEnabledFeatureFlags } from "../../../../../../../../db/tables/featureFlags"; export default async function FixPage() { const session = await getServerSession(); @@ -31,6 +32,9 @@ export default async function FixPage() { if (typeof profileId === "number") { await refreshStoredScanResults(profileId); } + const enabledFeatureFlags = await getEnabledFeatureFlags({ + email: session.user.email, + }); const scanData = await getScanResultsWithBroker( profileId, @@ -43,6 +47,9 @@ export default async function FixPage() { latestScanData: scanData, }; - const nextStep = getNextGuidedStep(stepDeterminationData); + const nextStep = getNextGuidedStep( + stepDeterminationData, + enabledFeatureFlags, + ); redirect(nextStep.href); } diff --git a/src/app/functions/server/dashboard.ts b/src/app/functions/server/dashboard.ts index 4a1801681cd..51f94cf2e6d 100644 --- a/src/app/functions/server/dashboard.ts +++ b/src/app/functions/server/dashboard.ts @@ -6,6 +6,8 @@ import { OnerepScanResultDataBrokerRow } from "knex/types/tables"; import { BreachDataTypes } from "../universal/breach"; import { RemovalStatusMap } from "../universal/scanResult"; import { SubscriberBreach } from "../../../utils/subscriberBreaches"; +import { DataBrokerRemovalStatusMap } from "../universal/dataBroker"; +import { FeatureFlagName } from "../../../db/tables/featureFlags"; export type DataPoints = { // shared @@ -96,6 +98,7 @@ export const dataClassKeyMap: Record = { export function getDashboardSummary( scannedResults: OnerepScanResultDataBrokerRow[], subscriberBreaches: SubscriberBreach[], + enabledFeatureFlags?: FeatureFlagName[], ): DashboardSummary { const summary: DashboardSummary = { dataBreachTotalNum: 0, @@ -204,15 +207,22 @@ export function getDashboardSummary( r.status === RemovalStatusMap.WaitingForVerification) && !isManuallyResolved; - // TODO: Waiting for criteria for data brokers under maintenance to be determined - // const isRemovalUnderMaintenance = - // r.broker_status === DataBrokerRemovalStatusMap.RemovalUnderMaintenance; + // TODO: MNTOR-3886 - Remove EnableRemovalUnderMaintenanceStep feature flag + // If the flag is disabled, include the data. + // If the flag is enabled, include the data only if the broker status is not + const isRemovalUnderMaintenance = + r.broker_status === DataBrokerRemovalStatusMap.RemovalUnderMaintenance; + + // The condition ensures that removal under maintenance is only considered when the flag is enabled. + /* c8 ignore next 3 */ + const countRemovalUnderMaintenanceData = + !enabledFeatureFlags?.includes("EnableRemovalUnderMaintenanceStep") || + !isRemovalUnderMaintenance; if (isInProgress) { - // TODO: Waiting for criteria for data brokers under maintenance to be determined - // if (!isRemovalUnderMaintenance) { - summary.dataBrokerInProgressNum++; - // } + if (countRemovalUnderMaintenanceData) { + summary.dataBrokerInProgressNum++; + } } else if (isAutoFixed) { summary.dataBrokerAutoFixedNum++; } else if (isManuallyResolved) { @@ -234,14 +244,13 @@ export function getDashboardSummary( summary.allDataPoints.familyMembers += r.relatives.length; if (isInProgress) { - // TODO: Waiting for criteria for data brokers under maintenance to be determined - // if (!isRemovalUnderMaintenance) { - summary.inProgressDataPoints.emailAddresses += r.emails.length; - summary.inProgressDataPoints.phoneNumbers += r.phones.length; - summary.inProgressDataPoints.addresses += r.addresses.length; - summary.inProgressDataPoints.familyMembers += r.relatives.length; - summary.dataBrokerInProgressDataPointsNum += dataPointsIncrement; - // } + if (countRemovalUnderMaintenanceData) { + summary.inProgressDataPoints.emailAddresses += r.emails.length; + summary.inProgressDataPoints.phoneNumbers += r.phones.length; + summary.inProgressDataPoints.addresses += r.addresses.length; + summary.inProgressDataPoints.familyMembers += r.relatives.length; + summary.dataBrokerInProgressDataPointsNum += dataPointsIncrement; + } } // for fixed data points: email, phones, addresses, relatives, full name (1) diff --git a/src/app/functions/server/getRelevantGuidedSteps.ts b/src/app/functions/server/getRelevantGuidedSteps.ts index 32b2e5412ee..fa04851b6da 100644 --- a/src/app/functions/server/getRelevantGuidedSteps.ts +++ b/src/app/functions/server/getRelevantGuidedSteps.ts @@ -83,7 +83,7 @@ export function isGuidedResolutionInProgress(stepId: StepLink["id"]) { export function getNextGuidedStep( data: StepDeterminationData, - enabledFeatureFlags?: FeatureFlagName[], + enabledFeatureFlags: FeatureFlagName[], afterStep?: StepLink["id"], ): StepLink { // Resisting the urge to add a state machine... ^.^ From 157a21d3628a7daf9efa9879f6405708dd168c24 Mon Sep 17 00:00:00 2001 From: Kaitlyn Date: Thu, 9 Jan 2025 10:46:44 -0500 Subject: [PATCH 2/5] fix unit tests --- .../server/getRelevantGuidedSteps.test.ts | 1048 +++++++++-------- 1 file changed, 575 insertions(+), 473 deletions(-) diff --git a/src/app/functions/server/getRelevantGuidedSteps.test.ts b/src/app/functions/server/getRelevantGuidedSteps.test.ts index ec7dcff95e7..1ed0b6f7981 100644 --- a/src/app/functions/server/getRelevantGuidedSteps.test.ts +++ b/src/app/functions/server/getRelevantGuidedSteps.test.ts @@ -45,14 +45,17 @@ describe("getNextGuidedStep", () => { describe("for non-US users", () => { it("links back to the dashboard if the user has no breaches", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard", id: "Done", @@ -63,19 +66,22 @@ describe("getNextGuidedStep", () => { it("links to the Credit Card step if the user's credit card has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.CreditCard]: 42 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.CreditCard]: 42 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/credit-card", id: "HighRiskCreditCard", @@ -86,24 +92,27 @@ describe("getNextGuidedStep", () => { it("links to the Credit Card step if the user's credit card has been breached, even if the user's bank account was also breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [ - { - [BreachDataTypes.CreditCard]: 42, - [BreachDataTypes.BankAccount]: 1337, - }, - ], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [ + { + [BreachDataTypes.CreditCard]: 42, + [BreachDataTypes.BankAccount]: 1337, + }, + ], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/credit-card", id: "HighRiskCreditCard", @@ -114,24 +123,27 @@ describe("getNextGuidedStep", () => { it("links to the Credit Card step if the user's credit card has been breached, even if the user's Social Security Number was also breached (because we have no recommendations for non-US SSN breaches)", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [ - { - [BreachDataTypes.CreditCard]: 42, - [BreachDataTypes.SSN]: 1337, - }, - ], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [ + { + [BreachDataTypes.CreditCard]: 42, + [BreachDataTypes.SSN]: 1337, + }, + ], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/credit-card", id: "HighRiskCreditCard", @@ -142,19 +154,22 @@ describe("getNextGuidedStep", () => { it("links to the Bank Account step if the user's bank account has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/bank-account", id: "HighRiskBankAccount", @@ -165,23 +180,26 @@ describe("getNextGuidedStep", () => { it("links to the Bank Account step if the user's bank account has been breached, even if their credit card has also been breached, if the latter has already been resolved", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.CreditCard]: 42 }], - isResolved: true, - }), - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.CreditCard]: 42 }], + isResolved: true, + }), + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/bank-account", id: "HighRiskBankAccount", @@ -192,23 +210,26 @@ describe("getNextGuidedStep", () => { it("links to the Bank Account step if the user's bank account has been breached, even if their PIN has also been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.PIN]: 42 }], - isResolved: false, - }), - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.PIN]: 42 }], + isResolved: false, + }), + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/bank-account", id: "HighRiskBankAccount", @@ -219,19 +240,22 @@ describe("getNextGuidedStep", () => { it("links to the PIN step if the user's PIN has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.PIN]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.PIN]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/pin", id: "HighRiskPin", @@ -242,19 +266,22 @@ describe("getNextGuidedStep", () => { it("links to the password step if the user's password has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.Passwords]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.Passwords]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/leaked-passwords/passwords", id: "LeakedPasswordsPassword", @@ -265,21 +292,24 @@ describe("getNextGuidedStep", () => { it("links to the security questions step if the user's security questions have been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [ - { [BreachDataTypes.SecurityQuestions]: 1337 }, - ], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [ + { [BreachDataTypes.SecurityQuestions]: 1337 }, + ], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/leaked-passwords/security-questions", id: "LeakedPasswordsSecurityQuestion", @@ -290,19 +320,22 @@ describe("getNextGuidedStep", () => { it("links to the phone number step if the user's phone number has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.Phone]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.Phone]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/security-recommendations/phone", id: "SecurityTipsPhone", @@ -313,19 +346,22 @@ describe("getNextGuidedStep", () => { it("links to the email step if the user's email has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.Email]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.Email]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/security-recommendations/email", id: "SecurityTipsEmail", @@ -336,19 +372,22 @@ describe("getNextGuidedStep", () => { it("links to the IP step if the user's IP has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "nl", - latestScanData: null, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.IP]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "nl", + latestScanData: null, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.IP]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/security-recommendations/ip", id: "SecurityTipsIp", @@ -374,14 +413,17 @@ describe("getNextGuidedStep", () => { it("links back to the dashboard if the user has no breaches", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard", id: "Done", @@ -392,17 +434,20 @@ describe("getNextGuidedStep", () => { it("links to the scan if the user has not run a scan yet", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: { - scan: null, - results: [], - }, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: { + scan: null, + results: [], + }, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/data-broker-profiles/start-free-scan", id: "Scan", @@ -413,26 +458,29 @@ describe("getNextGuidedStep", () => { it("links to the scan if the user has a scan in progress and not all scan results are resolved", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: { - scan: { - ...completedScan.scan!, - onerep_scan_status: "in_progress", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: { + scan: { + ...completedScan.scan!, + onerep_scan_status: "in_progress", + }, + results: [ + createRandomScanResult({ + status: "new", + manually_resolved: false, + broker_status: "active", + }), + ], + }, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", }, - results: [ - createRandomScanResult({ - status: "new", - manually_resolved: false, - broker_status: "active", - }), - ], - }, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/data-broker-profiles/start-free-scan", id: "Scan", @@ -443,25 +491,28 @@ describe("getNextGuidedStep", () => { it("links to the scan if the user has a completed scan and not all scan results are resolved", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: { - scan: { - ...completedScan.scan!, - onerep_scan_status: "finished", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: { + scan: { + ...completedScan.scan!, + onerep_scan_status: "finished", + }, + results: [ + createRandomScanResult({ + status: "new", + manually_resolved: false, + }), + ], + }, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", }, - results: [ - createRandomScanResult({ - status: "new", - manually_resolved: false, - }), - ], - }, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/data-broker-profiles/start-free-scan", id: "Scan", @@ -472,97 +523,109 @@ describe("getNextGuidedStep", () => { it("does not link to the scan if the user has a completed scan and all scan results are being opted out", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: { - scan: { - ...completedScan.scan!, - onerep_scan_status: "finished", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: { + scan: { + ...completedScan.scan!, + onerep_scan_status: "finished", + }, + results: [ + createRandomScanResult({ + status: "optout_in_progress", + manually_resolved: false, + }), + ], + }, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", }, - results: [ - createRandomScanResult({ - status: "optout_in_progress", - manually_resolved: false, - }), - ], - }, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", }, - }).id, + [], + ).id, ).toBe("Done"); }); it("does not link to the scan if the user has a completed scan and all scan results have been removed", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: { - scan: { - ...completedScan.scan!, - onerep_scan_status: "finished", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: { + scan: { + ...completedScan.scan!, + onerep_scan_status: "finished", + }, + results: [ + createRandomScanResult({ + status: "removed", + manually_resolved: false, + }), + ], + }, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", }, - results: [ - createRandomScanResult({ - status: "removed", - manually_resolved: false, - }), - ], - }, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", }, - }).id, + [], + ).id, ).toBe("Done"); }); it("does not link to the scan if the user has a completed scan and all scan results are waiting for verification", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: { - scan: { - ...completedScan.scan!, - onerep_scan_status: "finished", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: { + scan: { + ...completedScan.scan!, + onerep_scan_status: "finished", + }, + results: [ + createRandomScanResult({ + status: "waiting_for_verification", + manually_resolved: false, + }), + ], + }, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", }, - results: [ - createRandomScanResult({ - status: "waiting_for_verification", - manually_resolved: false, - }), - ], - }, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", }, - }).id, + [], + ).id, ).toBe("Done"); }); it("does not link to the scan if the user has a completed scan and all scan results are manually resolved", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: { - scan: { - ...completedScan.scan!, - onerep_scan_status: "finished", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: { + scan: { + ...completedScan.scan!, + onerep_scan_status: "finished", + }, + results: [ + createRandomScanResult({ + status: "new", + manually_resolved: true, + }), + ], + }, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", }, - results: [ - createRandomScanResult({ - status: "new", - manually_resolved: true, - }), - ], - }, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", }, - }).id, + [], + ).id, ).toBe("Done"); }); @@ -603,26 +666,29 @@ describe("getNextGuidedStep", () => { // TODO: MNTOR-3886 - Remove EnableRemovalUnderMaintenanceStep feature flag it("does not link to the removal under maintenance step if the feature flag is off", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: { - scan: { - ...completedScan.scan!, - onerep_scan_status: "finished", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: { + scan: { + ...completedScan.scan!, + onerep_scan_status: "finished", + }, + results: [ + createRandomScanResult({ + status: "optout_in_progress", + manually_resolved: false, + broker_status: "removal_under_maintenance", + }), + ], + }, + subscriberBreaches: [], + user: { + email: "arbitrary@example.com", }, - results: [ - createRandomScanResult({ - status: "optout_in_progress", - manually_resolved: false, - broker_status: "removal_under_maintenance", - }), - ], - }, - subscriberBreaches: [], - user: { - email: "arbitrary@example.com", }, - }).id, + [], + ).id, ).toBe("Done"); }); @@ -861,19 +927,22 @@ describe("getNextGuidedStep", () => { it("links to the Credit Card step if the user's credit card has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.CreditCard]: 42 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.CreditCard]: 42 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/credit-card", id: "HighRiskCreditCard", @@ -884,24 +953,27 @@ describe("getNextGuidedStep", () => { it("links to the Credit Card step if the user's credit card has been breached, even if the user's bank account was also breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [ - { - [BreachDataTypes.CreditCard]: 42, - [BreachDataTypes.BankAccount]: 1337, - }, - ], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [ + { + [BreachDataTypes.CreditCard]: 42, + [BreachDataTypes.BankAccount]: 1337, + }, + ], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/credit-card", id: "HighRiskCreditCard", @@ -912,24 +984,27 @@ describe("getNextGuidedStep", () => { it("links to the Credit Card step if the user's credit card has been breached, even if the user's Social Security Number was also breached (because we have no recommendations for non-US SSN breaches)", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [ - { - [BreachDataTypes.CreditCard]: 42, - [BreachDataTypes.SSN]: 1337, - }, - ], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [ + { + [BreachDataTypes.CreditCard]: 42, + [BreachDataTypes.SSN]: 1337, + }, + ], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/credit-card", id: "HighRiskCreditCard", @@ -940,19 +1015,22 @@ describe("getNextGuidedStep", () => { it("links to the Bank Account step if the user's bank account has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/bank-account", id: "HighRiskBankAccount", @@ -963,23 +1041,26 @@ describe("getNextGuidedStep", () => { it("links to the Bank Account step if the user's bank account has been breached, even if their credit card has also been breached, if the latter has already been resolved", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.CreditCard]: 42 }], - isResolved: true, - }), - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.CreditCard]: 42 }], + isResolved: true, + }), + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/bank-account", id: "HighRiskBankAccount", @@ -990,23 +1071,26 @@ describe("getNextGuidedStep", () => { it("links to the Bank Account step if the user's bank account has been breached, even if their PIN has also been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.PIN]: 42 }], - isResolved: false, - }), - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.PIN]: 42 }], + isResolved: false, + }), + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.BankAccount]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/bank-account", id: "HighRiskBankAccount", @@ -1017,19 +1101,22 @@ describe("getNextGuidedStep", () => { it("links to the PIN step if the user's PIN has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.PIN]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.PIN]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/high-risk-data-breaches/pin", id: "HighRiskPin", @@ -1040,19 +1127,22 @@ describe("getNextGuidedStep", () => { it("links to the password step if the user's password has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.Passwords]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.Passwords]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/leaked-passwords/passwords", id: "LeakedPasswordsPassword", @@ -1063,21 +1153,24 @@ describe("getNextGuidedStep", () => { it("links to the security questions step if the user's security questions have been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [ - { [BreachDataTypes.SecurityQuestions]: 1337 }, - ], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [ + { [BreachDataTypes.SecurityQuestions]: 1337 }, + ], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/leaked-passwords/security-questions", id: "LeakedPasswordsSecurityQuestion", @@ -1088,19 +1181,22 @@ describe("getNextGuidedStep", () => { it("links to the phone number step if the user's phone number has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.Phone]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.Phone]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/security-recommendations/phone", id: "SecurityTipsPhone", @@ -1111,19 +1207,22 @@ describe("getNextGuidedStep", () => { it("links to the email step if the user's email has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.Email]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.Email]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/security-recommendations/email", id: "SecurityTipsEmail", @@ -1134,19 +1233,22 @@ describe("getNextGuidedStep", () => { it("links to the IP step if the user's IP has been breached", () => { expect( - getNextGuidedStep({ - countryCode: "us", - latestScanData: completedScan, - subscriberBreaches: [ - createRandomBreach({ - dataClassesEffected: [{ [BreachDataTypes.IP]: 1337 }], - isResolved: false, - }), - ], - user: { - email: "arbitrary@example.com", + getNextGuidedStep( + { + countryCode: "us", + latestScanData: completedScan, + subscriberBreaches: [ + createRandomBreach({ + dataClassesEffected: [{ [BreachDataTypes.IP]: 1337 }], + isResolved: false, + }), + ], + user: { + email: "arbitrary@example.com", + }, }, - }), + [], + ), ).toStrictEqual({ href: "/user/dashboard/fix/security-recommendations/ip", id: "SecurityTipsIp", From 1dbc11fd00bbc7ba578b24b34bdda6ef8a102f53 Mon Sep 17 00:00:00 2001 From: Kaitlyn Date: Mon, 13 Jan 2025 08:29:33 -0500 Subject: [PATCH 3/5] add feature flags to manualremovalview and welcome view --- .../data-broker-profiles/manual-remove/ManualRemoveView.tsx | 6 +++++- .../welcome-to-plus/WelcomeToPlusView.tsx | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/data-broker-profiles/manual-remove/ManualRemoveView.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/data-broker-profiles/manual-remove/ManualRemoveView.tsx index 2fcfd55bdf2..ab0cc3abaac 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/data-broker-profiles/manual-remove/ManualRemoveView.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/data-broker-profiles/manual-remove/ManualRemoveView.tsx @@ -43,7 +43,11 @@ export function ManualRemoveView(props: Props) { const l10n = useL10n(); const [activeExposureCardKey, setActiveExposureCardKey] = useState(0); - const summary = getDashboardSummary(props.scanData.results, props.breaches); + const summary = getDashboardSummary( + props.scanData.results, + props.breaches, + props.enabledFeatureFlags, + ); const countOfDataBrokerProfiles = props.scanData.results.length; const estimatedTime = countOfDataBrokerProfiles * 10; // 10 minutes per data broker site. diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/data-broker-profiles/welcome-to-plus/WelcomeToPlusView.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/data-broker-profiles/welcome-to-plus/WelcomeToPlusView.tsx index c6b81318849..3ba550fd78d 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/data-broker-profiles/welcome-to-plus/WelcomeToPlusView.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/data-broker-profiles/welcome-to-plus/WelcomeToPlusView.tsx @@ -39,6 +39,7 @@ export function WelcomeToPlusView(props: Props) { const summary = getDashboardSummary( scanResultsInProgress, props.data.subscriberBreaches, + props.enabledFeatureFlags, ); const dataPointReduction = getDataPointReduction(summary); From 2c60b4737315522ce6e8063102211748f9148a0c Mon Sep 17 00:00:00 2001 From: Kaitlyn Date: Tue, 14 Jan 2025 08:06:11 -0500 Subject: [PATCH 4/5] add feature flag to resolution container --- .../user/(dashboard)/dashboard/fix/ResolutionContainer.tsx | 3 +++ .../fix/high-risk-data-breaches/HighRiskBreachLayout.tsx | 1 + .../dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx | 1 + .../security-recommendations/SecurityRecommendationsLayout.tsx | 1 + 4 files changed, 6 insertions(+) diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/ResolutionContainer.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/ResolutionContainer.tsx index 48cdd16065f..7ddd3930004 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/ResolutionContainer.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/ResolutionContainer.tsx @@ -12,6 +12,7 @@ import styles from "./ResolutionContainer.module.scss"; import { ProgressCard } from "../../../../../../../components/client/ProgressCard"; import { StepDeterminationData } from "../../../../../../../functions/server/getRelevantGuidedSteps"; import { getDashboardSummary } from "../../../../../../../functions/server/dashboard"; +import { FeatureFlagName } from "../../../../../../../../db/tables/featureFlags"; type ResolutionContainerProps = { type: "highRisk" | "leakedPasswords" | "securityRecommendations"; @@ -25,6 +26,7 @@ type ResolutionContainerProps = { data: StepDeterminationData; label?: string; cta?: ReactNode; + enabledFeatureFlags: FeatureFlagName[]; }; export const ResolutionContainer = (props: ResolutionContainerProps) => { @@ -40,6 +42,7 @@ export const ResolutionContainer = (props: ResolutionContainerProps) => { const resolutionSummary = getDashboardSummary( props.data.latestScanData?.results ?? [], props.data.subscriberBreaches, + props.enabledFeatureFlags, ); return ( diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 325ea84e3df..b18a4ccbb29 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -171,6 +171,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { illustration={illustration} isPremiumUser={hasPremium(props.data.user)} isEligibleForPremium={props.isEligibleForPremium} + enabledFeatureFlags={props.enabledFeatureFlags} cta={ !isStepDone && ( <> diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx index dc95d5c316b..5501fe0ecf3 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx @@ -198,6 +198,7 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { title={title} illustration={illustration} isPremiumUser={hasPremium(props.data.user)} + enabledFeatureFlags={props.enabledFeatureFlags} cta={ !isStepDone && ( <> diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index 61268dcfd4b..f4511fca159 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -164,6 +164,7 @@ export function SecurityRecommendationsLayout( title={title} illustration={illustration} isPremiumUser={hasPremium(props.data.user)} + enabledFeatureFlags={props.enabledFeatureFlags} cta={ !isStepDone && (