1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-03 02:03:53 +00:00

feat(risk-insights): enrich newApplications$ observable with full metrics

- Update newApplications$ to return NewApplicationDetail[] instead of string[]
- Combine applicationData (for reviewedDate filter) with reportData (for metrics)
- Include atRiskPasswordCount, passwordCount, and atRiskMemberCount per application
- Filter out applications without matching report details
- Update distinctUntilChanged to compare all application properties
- Provides complete data at source, eliminates need for downstream filtering

Part 2 of 5: Orchestrator now returns enriched new application data.
This commit is contained in:
Alex
2025-10-31 16:11:44 -04:00
parent 07528a5bc3
commit 4e5f2d4b8d

View File

@@ -52,6 +52,7 @@ import { RiskInsightsEnrichedData } from "../../models/report-data-service.types
import {
CipherHealthReport,
MemberDetails,
NewApplicationDetail,
OrganizationReportApplication,
OrganizationReportSummary,
ReportStatus,
@@ -98,18 +99,52 @@ export class RiskInsightsOrchestratorService {
enrichedReportData$ = this._enrichedReportDataSubject.asObservable();
// New applications that haven't been reviewed (reviewedDate === null)
newApplications$: Observable<string[]> = this.rawReportData$.pipe(
// Returns full application details for display in the new applications dialog
newApplications$: Observable<NewApplicationDetail[]> = this.rawReportData$.pipe(
map((reportState) => {
if (!reportState.data?.applicationData) {
if (!reportState.data?.applicationData || !reportState.data?.reportData) {
return [];
}
return reportState.data.applicationData
.filter((app) => app.reviewedDate === null)
.map((app) => app.applicationName);
// Get applications that haven't been reviewed
const unreviewedApps = reportState.data.applicationData.filter(
(app) => app.reviewedDate === null,
);
// Map to NewApplicationDetail with full data from reportData
return unreviewedApps
.map((app) => {
// Find matching report data for this application
const reportDetail = reportState.data!.reportData.find(
(report) => report.applicationName === app.applicationName,
);
// Skip if no matching report detail found
if (!reportDetail) {
return null;
}
return {
applicationName: app.applicationName,
atRiskPasswordCount: reportDetail.atRiskPasswordCount,
passwordCount: reportDetail.passwordCount,
atRiskMemberCount: reportDetail.atRiskMemberCount,
} as NewApplicationDetail;
})
.filter((app): app is NewApplicationDetail => app !== null);
}),
distinctUntilChanged((prev, curr) => {
if (prev.length !== curr.length) {
return false;
}
return prev.every(
(prevApp, i) =>
prevApp.applicationName === curr[i].applicationName &&
prevApp.atRiskPasswordCount === curr[i].atRiskPasswordCount &&
prevApp.passwordCount === curr[i].passwordCount &&
prevApp.atRiskMemberCount === curr[i].atRiskMemberCount,
);
}),
distinctUntilChanged(
(prev, curr) => prev.length === curr.length && prev.every((app, i) => app === curr[i]),
),
shareReplay({ bufferSize: 1, refCount: true }),
);