Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MNTOR-3893 - Fix QA Custom tool for mocked data brokers #5493

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ import { Button } from "../../../../../../components/client/Button";
import { CreateFeatureFlagRequestBody } from "../../../../../../api/v1/admin/feature-flags/route";
import { FeatureFlagName } from "../../../../../../../db/tables/featureFlags";

export const NewFlagEditor = (props: { flagName: FeatureFlagName }) => {
export const NewFlagEditor = (props: {
flagName: FeatureFlagName;
adminOnly: boolean;
}) => {
return (
<FlagEditor
adminOnly={props.adminOnly}
flagName={props.flagName}
isEnabled={false}
onToggleEnable={async (isEnabled) => {
Expand Down Expand Up @@ -48,6 +52,7 @@ export const NewFlagEditor = (props: { flagName: FeatureFlagName }) => {
export const ExistingFlagEditor = (props: { flag: FeatureFlagRow }) => {
return (
<FlagEditor
adminOnly={false}
flagName={props.flag.name}
isEnabled={props.flag.is_enabled}
onToggleEnable={async (isEnabled) => {
Expand Down Expand Up @@ -82,6 +87,7 @@ type Props = {
isEnabled: boolean;
onToggleEnable: (isEnabled: boolean) => Promise<void>;
allowList: string[];
adminOnly: boolean;
onUpdateAllowlist: (allowList: string[]) => Promise<void>;
};
const FlagEditor = (props: Props) => {
Expand Down Expand Up @@ -114,6 +120,7 @@ const FlagEditor = (props: Props) => {
variant="secondary"
onPress={() => void setIsEnabled(true)}
small
disabled={props.adminOnly}
>
Enable for everyone
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import { notFound, redirect } from "next/navigation";
import { getServerSession } from "../../../../../functions/server/getServerSession";
import {
allFeatureFlags,
FeatureFlagName,
featureFlagNames,
featureFlagsAdminOnly,
getAllFeatureFlags,
} from "../../../../../../db/tables/featureFlags";
import { isAdmin } from "../../../../../api/utils/auth";
Expand Down Expand Up @@ -43,7 +44,7 @@ export default async function FeatureFlagPage() {
* Elements in this array are either existing flags that are disabled,
* or names of flags that are not known in the database yet.
*/
const disabledFlags = featureFlagNames
const disabledFlags = allFeatureFlags
.map((flagName) => {
return featureFlags.find((flag) => flag.name === flagName) ?? flagName;
})
Expand All @@ -53,6 +54,11 @@ export default async function FeatureFlagPage() {
)
.reverse();

const isAdminOnlyFeature = (flagName: string) =>
featureFlagsAdminOnly.includes(
flagName as (typeof featureFlagsAdminOnly)[number],
);

return (
<div className={styles.wrapper}>
<nav className={styles.tabBar}>
Expand All @@ -77,7 +83,11 @@ export default async function FeatureFlagPage() {
<div className={styles.flagList}>
{disabledFlags.map((flagOrFlagName) => {
return typeof flagOrFlagName === "string" ? (
<NewFlagEditor key={flagOrFlagName} flagName={flagOrFlagName} />
<NewFlagEditor
key={flagOrFlagName}
flagName={flagOrFlagName}
adminOnly={isAdminOnlyFeature(flagOrFlagName)}
/>
) : (
<ExistingFlagEditor
key={flagOrFlagName.name}
Expand All @@ -92,7 +102,7 @@ export default async function FeatureFlagPage() {
.filter(
(flag) =>
flag.is_enabled &&
featureFlagNames.includes(flag.name as FeatureFlagName),
allFeatureFlags.includes(flag.name as FeatureFlagName),
)
.map((flag) => (
<ExistingFlagEditor key={flag.name} flag={flag} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import React, { useState, useEffect } from "react";
import styles from "./ConfigPage.module.scss";
import { RemovalStatus } from "../../../../../functions/universal/scanResult";
import { DataBrokerRemovalStatus } from "../../../../../functions/universal/dataBroker";

interface QaBrokerDataCounts {
onerep_scan_result_id?: number;
Expand All @@ -23,6 +25,9 @@ interface QaBrokerDataCounts {
status: string;
manually_resolved: string;
optout_attempts?: number;
scan_result_status: RemovalStatus;
broker_status: DataBrokerRemovalStatus;
url: string;
}

const endpointBase = "/api/v1/admin/qa-customs/onerep";
Expand Down Expand Up @@ -63,6 +68,9 @@ const OnerepConfigPage = (props: Props) => {
last_name: "",
status: "new",
manually_resolved: "false",
scan_result_status: "new",
broker_status: "active",
url: "",
};

// Temporary state to hold form input for a new broker
Expand Down Expand Up @@ -383,6 +391,25 @@ const OnerepConfigPage = (props: Props) => {
</select>
</label>

<label>
Broker status:
<select
name="status"
value={newBroker.broker_status}
onChange={(e) => void handleChange(e)}
>
<option value="active">Active</option>
<option value="on_hold">In Progress</option>
<option value="ceased_operation">Ceased operation</option>
<option value="scan_under_maintenance">
Scan under maintenance
</option>
<option value="removal_under_maintenance">
Removal under maintenance
</option>
</select>
</label>

<label className={styles.label}>
Optout Attempts:
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "../../../../../../../functions/universal/user";
import {
getLatestScanForProfileByReason,
getMockedScanResults,
getScanResultsWithBroker,
getScansCountForProfile,
} from "../../../../../../../../db/tables/onerep_scans";
Expand All @@ -33,7 +34,10 @@ import {
getPremiumSubscriptionUrl,
} from "../../../../../../../functions/server/getPremiumSubscriptionInfo";
import { refreshStoredScanResults } from "../../../../../../../functions/server/refreshStoredScanResults";
import { getEnabledFeatureFlags } from "../../../../../../../../db/tables/featureFlags";
import {
allFeatureFlags,
getEnabledFeatureFlags,
} from "../../../../../../../../db/tables/featureFlags";
import { getAttributionsFromCookiesOrDb } from "../../../../../../../functions/server/attributions";
import { checkSession } from "../../../../../../../functions/server/checkSession";
import { isPrePlusUser } from "../../../../../../../functions/server/isPrePlusUser";
Expand Down Expand Up @@ -112,6 +116,14 @@ export default async function DashboardPage(props: Props) {
hasPremium(session.user),
);

const mockScanResultsData = await getMockedScanResults(profileId ?? 100);

const showCustomData = allFeatureFlags.includes(
"CustomDataBrokersAndBreaches",
);

const brokerData = showCustomData ? mockScanResultsData : latestScan;

const scanCount =
typeof profileId === "number"
? await getScansCountForProfile(profileId)
Expand Down Expand Up @@ -161,7 +173,7 @@ export default async function DashboardPage(props: Props) {
user={session.user}
isEligibleForPremium={userIsEligibleForPremium}
isEligibleForFreeScan={userIsEligibleForFreeScan}
userScanData={latestScan}
userScanData={brokerData}
userBreaches={subBreaches}
enabledFeatureFlags={enabledFeatureFlags}
monthlySubscriptionUrl={`${monthlySubscriptionUrl}&${additionalSubplatParams.toString()}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { getCountryCode } from "../../../../../../../../../functions/server/getCountryCode";
import { headers } from "next/headers";
import {
getMockedScanResults,
getScanResultsWithBroker,
getScanResultsWithBrokerUnderMaintenance,
} from "../../../../../../../../../../db/tables/onerep_scans";
Expand All @@ -20,6 +21,7 @@ import { getSubscriberEmails } from "../../../../../../../../../functions/server
import { RemovalUnderMaintenanceView } from "./RemovalUnderMaintenanceView";
import { hasPremium } from "../../../../../../../../../functions/universal/user";
import { getEnabledFeatureFlags } from "../../../../../../../../../../db/tables/featureFlags";
import { getQaToggleRow } from "../../../../../../../../../../db/tables/qa_customs";

export default async function RemovalUnderMaintenance() {
const session = await getServerSession();
Expand Down Expand Up @@ -64,19 +66,30 @@ export default async function RemovalUnderMaintenance() {
"DataBrokerManualRemoval",
);

if (
scansWithRemovalUnderMaintenance?.results.length === 0 ||
!scansWithRemovalUnderMaintenance
) {
const mockScanResultsData = await getMockedScanResults(profileId ?? 100);
const qaToggles = await getQaToggleRow(profileId);
let showCustomBrokers = false;

if (qaToggles) {
showCustomBrokers = qaToggles.show_custom_brokers;
}

const brokerData = showCustomBrokers
? mockScanResultsData
: scansWithRemovalUnderMaintenance;

if (brokerData?.results.length === 0 || !brokerData) {
redirect(getNextStep.href);
}

console.log({ brokerData });

const subscriberEmails = await getSubscriberEmails(session.user);

return (
<RemovalUnderMaintenanceView
stepDeterminationData={data}
data={scansWithRemovalUnderMaintenance}
data={brokerData}
subscriberEmails={subscriberEmails}
enabledFeatureFlags={enabledFeatureFlags}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const isMatchingContent = (
) => {
const { hasExposures, hasUnresolvedBreaches, hasUnresolvedBrokers } =
contentConditions;

// If a user does not have any exposures it’s also not possible to have unresolved ones.
// This check is meant to avoid adding invalid conditions in `getUserDashboardState`.
/* c8 ignore next 8 */
Expand Down
6 changes: 6 additions & 0 deletions src/app/api/v1/admin/qa-customs/onerep/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ export async function POST(req: NextRequest) {
const status = body.status || "new";
const manually_resolved = body.manually_resolved || false;
const optout_attempts = body.optout_attempts || null;
const scan_result_status = body.scan_result_status || "new";
const broker_status = body.broker_status || "active";
const url = body.url || "";

const brokerData: QaBrokerData = {
onerep_profile_id,
Expand All @@ -107,6 +110,9 @@ export async function POST(req: NextRequest) {
status,
manually_resolved,
optout_attempts,
scan_result_status,
broker_status,
url,
};
try {
await addQaCustomBroker(brokerData);
Expand Down
12 changes: 10 additions & 2 deletions src/db/tables/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,15 @@ export const featureFlagNames = [
"CirrusV2",
"DataBrokerRemovalAttempts",
] as const;
export type FeatureFlagName = (typeof featureFlagNames)[number];

export const featureFlagsAdminOnly = ["CustomDataBrokersAndBreaches"] as const;

export const allFeatureFlags = [
...featureFlagNames,
...featureFlagsAdminOnly,
] as const;

export type FeatureFlagName = (typeof allFeatureFlags)[number];

/**
* @param options
Expand Down Expand Up @@ -92,7 +100,7 @@ export async function getEnabledFeatureFlags(
const forcedFeatureFlagsFiltered = forcedFeatureFlags
.split(",")
.filter((forcedFeatureFlag) =>
featureFlagNames.includes(forcedFeatureFlag as FeatureFlagName),
allFeatureFlags.includes(forcedFeatureFlag as FeatureFlagName),
);
return [
...new Set([...enabledFlagNames, ...forcedFeatureFlagsFiltered]),
Expand Down
23 changes: 22 additions & 1 deletion src/db/tables/onerep_scans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import {
} from "knex/types/tables";
import { RemovalStatus } from "../../app/functions/universal/scanResult.js";
import { CONST_DAY_MILLISECONDS } from "../../constants.ts";
import { getQaCustomBrokers, getQaToggleRow } from "./qa_customs.ts";
import {
getAllQaCustomBrokers,
getQaCustomBrokers,
getQaToggleRow,
} from "./qa_customs.ts";

const knex = createDbConnection();

Expand Down Expand Up @@ -450,6 +454,23 @@ async function getScanResultsWithBrokerUnderMaintenance(
return { results: scanResults } as LatestOnerepScanData;
}

export async function getMockedScanResults(
onerepProfileId: number,
): Promise<LatestOnerepScanData> {
if (onerepProfileId === null) {
return {
scan: null,
results: [],
} as LatestOnerepScanData;
}

const scan = await getLatestOnerepScan(onerepProfileId);
const scanResults: OnerepScanResultDataBrokerRow[] | OnerepScanResultRow[] =
await getAllQaCustomBrokers(onerepProfileId);

return { scan: scan ?? null, results: scanResults } as LatestOnerepScanData;
}

async function getScanResultsWithBroker(
onerepProfileId: number | null,
hasPremium: boolean | null,
Expand Down
16 changes: 13 additions & 3 deletions src/db/tables/qa_customs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
* 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 { OnerepScanResultRow } from "knex/types/tables";
import {
OnerepScanResultDataBrokerRow,
OnerepScanResultRow,
} from "knex/types/tables";
import { logger } from "../../app/functions/server/logging";
import createDbConnection from "../connect";
import { getOnerepProfileId } from "./subscribers";
import { HibpLikeDbBreach } from "../../utils/hibp";
import { RemovalStatus } from "../../app/functions/universal/scanResult";
import { DataBrokerRemovalStatus } from "../../app/functions/universal/dataBroker";

const knex = createDbConnection();

Expand All @@ -25,6 +30,9 @@ interface QaBrokerData {
status: string;
manually_resolved: boolean;
optout_attempts: number;
scan_result_status: RemovalStatus;
broker_status: DataBrokerRemovalStatus;
url: string;
}

interface QaBreachData {
Expand Down Expand Up @@ -123,10 +131,10 @@ async function addQaCustomBroker(brokerData: QaBrokerData): Promise<void> {

async function getAllQaCustomBrokers(
onerep_profile_id: number,
): Promise<QaBrokerData[]> {
): Promise<OnerepScanResultDataBrokerRow[] | OnerepScanResultRow[]> {
const res = (await knex("qa_custom_brokers")
.where("onerep_profile_id", onerep_profile_id)
.select("*")) as QaBrokerData[];
.select("*")) as OnerepScanResultDataBrokerRow[];
return res;
}

Expand Down Expand Up @@ -190,11 +198,13 @@ async function getQaToggleRow(emailHashOrOneRepId: string | number | null) {
if (emailHashOrOneRepId === null || envIsProd()) {
return null;
}
// for email sha
if (typeof emailHashOrOneRepId === "string") {
return (await knex("qa_custom_toggles")
.select("*")
.where("email_hash", emailHashOrOneRepId)
.first()) as QaToggleRow;
// for onerep ID
} else if (typeof emailHashOrOneRepId === "number") {
return (await knex("qa_custom_toggles")
.select("*")
Expand Down
Loading