in src/app/functions/server/dashboard.ts [98:445]
export function getDashboardSummary(
scannedResults: OnerepScanResultDataBrokerRow[],
subscriberBreaches: SubscriberBreach[],
enabledFeatureFlags?: FeatureFlagName[],
): DashboardSummary {
const summary: DashboardSummary = {
dataBreachTotalNum: 0,
dataBreachResolvedNum: 0,
dataBreachUnresolvedNum: 0,
dataBreachTotalDataPointsNum: 0,
dataBreachFixedDataPointsNum: 0,
dataBrokerTotalNum: scannedResults.length,
dataBrokerTotalDataPointsNum: 0,
dataBrokerAutoFixedNum: 0,
dataBrokerRemovalUnderMaintenance: 0,
dataBrokerAutoFixedDataPointsNum: 0,
dataBrokerInProgressNum: 0,
dataBrokerInProgressDataPointsNum: 0,
dataBrokerManuallyResolvedNum: 0,
dataBrokerManuallyResolvedDataPointsNum: 0,
totalDataPointsNum: 0,
allDataPoints: {
emailAddresses: 0,
phoneNumbers: 0,
addresses: 0,
familyMembers: 0,
// data breaches
socialSecurityNumbers: 0,
ipAddresses: 0,
passwords: 0,
creditCardNumbers: 0,
pins: 0,
securityQuestions: 0,
bankAccountNumbers: 0,
},
unresolvedDataPoints: {
emailAddresses: 0,
phoneNumbers: 0,
addresses: 0,
familyMembers: 0,
// data breaches
socialSecurityNumbers: 0,
ipAddresses: 0,
passwords: 0,
creditCardNumbers: 0,
pins: 0,
securityQuestions: 0,
bankAccountNumbers: 0,
},
inProgressDataPoints: {
emailAddresses: 0,
phoneNumbers: 0,
addresses: 0,
familyMembers: 0,
// data breaches
socialSecurityNumbers: 0,
ipAddresses: 0,
passwords: 0,
creditCardNumbers: 0,
pins: 0,
securityQuestions: 0,
bankAccountNumbers: 0,
},
fixedDataPoints: {
emailAddresses: 0,
phoneNumbers: 0,
addresses: 0,
familyMembers: 0,
// data breaches
socialSecurityNumbers: 0,
ipAddresses: 0,
passwords: 0,
creditCardNumbers: 0,
pins: 0,
securityQuestions: 0,
bankAccountNumbers: 0,
},
manuallyResolvedDataBrokerDataPoints: {
emailAddresses: 0,
phoneNumbers: 0,
addresses: 0,
familyMembers: 0,
// data breaches
socialSecurityNumbers: 0,
ipAddresses: 0,
passwords: 0,
creditCardNumbers: 0,
pins: 0,
securityQuestions: 0,
bankAccountNumbers: 0,
},
unresolvedSanitizedDataPoints: [],
fixedSanitizedDataPoints: [],
};
// calculate broker summary from scanned results
if (scannedResults) {
scannedResults.forEach((r) => {
// check removal status
const isManuallyResolved = r.manually_resolved;
const isAutoFixed =
r.status === RemovalStatusMap.Removed && !isManuallyResolved;
const isInProgress =
(r.status === RemovalStatusMap.OptOutInProgress ||
r.status === RemovalStatusMap.WaitingForVerification) &&
!isManuallyResolved;
// 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) {
if (countRemovalUnderMaintenanceData) {
summary.dataBrokerInProgressNum++;
}
} else if (isAutoFixed) {
summary.dataBrokerAutoFixedNum++;
} else if (isManuallyResolved) {
summary.dataBrokerManuallyResolvedNum++;
}
// total data points: add email, phones, addresses, relatives, full name (1)
const dataPointsIncrement =
r.emails.length +
r.phones.length +
r.addresses.length +
r.relatives.length;
summary.totalDataPointsNum += dataPointsIncrement;
summary.dataBrokerTotalDataPointsNum += dataPointsIncrement;
// for all data points: email, phones, addresses, relatives, full name (1)
summary.allDataPoints.emailAddresses += r.emails.length;
summary.allDataPoints.phoneNumbers += r.phones.length;
summary.allDataPoints.addresses += r.addresses.length;
summary.allDataPoints.familyMembers += r.relatives.length;
if (isInProgress) {
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)
if (isAutoFixed) {
summary.fixedDataPoints.emailAddresses += r.emails.length;
summary.fixedDataPoints.phoneNumbers += r.phones.length;
summary.fixedDataPoints.addresses += r.addresses.length;
summary.fixedDataPoints.familyMembers += r.relatives.length;
summary.dataBrokerAutoFixedDataPointsNum += dataPointsIncrement;
}
if (isManuallyResolved) {
summary.manuallyResolvedDataBrokerDataPoints.emailAddresses +=
r.emails.length;
summary.manuallyResolvedDataBrokerDataPoints.phoneNumbers +=
r.phones.length;
summary.manuallyResolvedDataBrokerDataPoints.addresses +=
r.addresses.length;
summary.manuallyResolvedDataBrokerDataPoints.familyMembers +=
r.relatives.length;
summary.dataBrokerManuallyResolvedDataPointsNum += dataPointsIncrement;
}
});
}
// calculate breaches summary from breaches data
// TODO: Modify after MNTOR-1947: Refactor user breaches object
subscriberBreaches.forEach((b) => {
// According to the type, b.dataClasses should always be defined, so unit
// tests always define it. That said, since the type was added later, I'd
// rather not remove the `?? []` just in case it is wrong, hence:
/* c8 ignore next */
const dataClasses = b.dataClasses ?? [];
// b.emailsAffected *should* always be non-empty, since `subscriberBreaches`
// was specifically constructed to contain all breaches that affect the
// user. However, some basic `git blame` archeology doesn't turn up any info
// on why the `?? 0` was added, so I'm leaving it in place for now:
/* c8 ignore next */
const increment = b.emailsAffected.length ?? 0;
// count emails
if (dataClasses.includes(BreachDataTypes.Email)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.emailAddresses += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.Email)) {
summary.fixedDataPoints.emailAddresses += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
// count phone numbers
if (dataClasses.includes(BreachDataTypes.Phone)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.phoneNumbers += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.Phone)) {
summary.fixedDataPoints.phoneNumbers += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
// count password
if (dataClasses.includes(BreachDataTypes.Passwords)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.passwords += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.Passwords)) {
summary.fixedDataPoints.passwords += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
// count ssn
if (dataClasses.includes(BreachDataTypes.SSN)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.socialSecurityNumbers += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.SSN)) {
summary.fixedDataPoints.socialSecurityNumbers += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
// count IP
if (dataClasses.includes(BreachDataTypes.IP)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.ipAddresses += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.IP)) {
summary.fixedDataPoints.ipAddresses += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
// count credit card
if (dataClasses.includes(BreachDataTypes.CreditCard)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.creditCardNumbers += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.CreditCard)) {
summary.fixedDataPoints.creditCardNumbers += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
// count pin numbers
if (dataClasses.includes(BreachDataTypes.PIN)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.pins += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.PIN)) {
summary.fixedDataPoints.pins += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
// count security questions
if (dataClasses.includes(BreachDataTypes.SecurityQuestions)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.securityQuestions += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.SecurityQuestions)) {
summary.fixedDataPoints.securityQuestions += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
if (dataClasses.includes(BreachDataTypes.BankAccount)) {
summary.totalDataPointsNum += increment;
summary.dataBreachTotalDataPointsNum += increment;
summary.allDataPoints.bankAccountNumbers += increment;
if (b.resolvedDataClasses.includes(BreachDataTypes.BankAccount)) {
summary.fixedDataPoints.bankAccountNumbers += increment;
summary.dataBreachFixedDataPointsNum += increment;
}
}
if (b.isResolved) summary.dataBreachResolvedNum++;
});
// count unique breaches
summary.dataBreachTotalNum = subscriberBreaches.length;
summary.dataBreachUnresolvedNum =
summary.dataBreachTotalNum - summary.dataBreachResolvedNum;
const isBreachesOnly = summary.dataBrokerTotalNum === 0;
// count unresolved data points
summary.unresolvedDataPoints = Object.keys(summary.allDataPoints).reduce(
(a, k) => {
a[k as keyof DataPoints] =
summary.allDataPoints[k as keyof DataPoints] -
summary.fixedDataPoints[k as keyof DataPoints] -
summary.inProgressDataPoints[k as keyof DataPoints] -
summary.manuallyResolvedDataBrokerDataPoints[k as keyof DataPoints];
return a;
},
{} as DataPoints,
);
// sanitize unresolved data points
summary.unresolvedSanitizedDataPoints = sanitizeDataPoints(
summary.unresolvedDataPoints,
summary.totalDataPointsNum -
summary.dataBreachFixedDataPointsNum -
summary.dataBrokerAutoFixedDataPointsNum -
summary.dataBrokerInProgressDataPointsNum -
summary.dataBrokerManuallyResolvedDataPointsNum,
isBreachesOnly,
);
// count fixed and manually resolved (data brokers) data points
const dataBrokerFixedManuallyResolved = Object.keys(
summary.fixedDataPoints,
).reduce((a, k) => {
a[k as keyof DataPoints] =
summary.fixedDataPoints[k as keyof DataPoints] +
summary.manuallyResolvedDataBrokerDataPoints[k as keyof DataPoints];
return a;
}, {} as DataPoints);
// sanitize fixed and removed data points
summary.fixedSanitizedDataPoints = sanitizeDataPoints(
dataBrokerFixedManuallyResolved,
summary.dataBreachFixedDataPointsNum +
summary.dataBrokerAutoFixedDataPointsNum +
summary.dataBrokerManuallyResolvedDataPointsNum,
isBreachesOnly,
);
return summary;
}