mirror of
https://github.com/bitwarden/browser
synced 2025-12-26 13:13:22 +00:00
Add progress tracking to risk insights report generation (#17199)
* Add progress tracking to risk insights report generation * added skeleton page loader
This commit is contained in:
@@ -107,6 +107,17 @@ export const ReportStatus = Object.freeze({
|
||||
|
||||
export type ReportStatus = (typeof ReportStatus)[keyof typeof ReportStatus];
|
||||
|
||||
export const ReportProgress = Object.freeze({
|
||||
FetchingMembers: 1,
|
||||
AnalyzingPasswords: 2,
|
||||
CalculatingRisks: 3,
|
||||
GeneratingReport: 4,
|
||||
Saving: 5,
|
||||
Complete: 6,
|
||||
} as const);
|
||||
|
||||
export type ReportProgress = (typeof ReportProgress)[keyof typeof ReportProgress];
|
||||
|
||||
export interface RiskInsightsData {
|
||||
id: OrganizationReportId;
|
||||
creationDate: Date;
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
OrganizationReportSummary,
|
||||
ReportStatus,
|
||||
ReportState,
|
||||
ReportProgress,
|
||||
ApplicationHealthReportDetail,
|
||||
} from "../../models/report-models";
|
||||
import { MemberCipherDetailsApiService } from "../api/member-cipher-details-api.service";
|
||||
@@ -128,6 +129,10 @@ export class RiskInsightsOrchestratorService {
|
||||
private _generateReportTriggerSubject = new BehaviorSubject<boolean>(false);
|
||||
generatingReport$ = this._generateReportTriggerSubject.asObservable();
|
||||
|
||||
// Report generation progress
|
||||
private _reportProgressSubject = new BehaviorSubject<ReportProgress | null>(null);
|
||||
reportProgress$ = this._reportProgressSubject.asObservable();
|
||||
|
||||
// --------------------------- Critical Application data ---------------------
|
||||
criticalReportResults$: Observable<RiskInsightsEnrichedData | null> = of(null);
|
||||
|
||||
@@ -631,19 +636,33 @@ export class RiskInsightsOrchestratorService {
|
||||
organizationId: OrganizationId,
|
||||
userId: UserId,
|
||||
): Observable<ReportState> {
|
||||
// Generate the report
|
||||
// Reset progress at the start
|
||||
this._reportProgressSubject.next(null);
|
||||
|
||||
this.logService.debug("[RiskInsightsOrchestratorService] Fetching member cipher details");
|
||||
this._reportProgressSubject.next(ReportProgress.FetchingMembers);
|
||||
|
||||
// Generate the report - fetch member ciphers and org ciphers in parallel
|
||||
const memberCiphers$ = from(
|
||||
this.memberCipherDetailsApiService.getMemberCipherDetails(organizationId),
|
||||
).pipe(map((memberCiphers) => flattenMemberDetails(memberCiphers)));
|
||||
|
||||
return forkJoin([this._ciphers$.pipe(take(1)), memberCiphers$]).pipe(
|
||||
tap(() => {
|
||||
this.logService.debug("[RiskInsightsOrchestratorService] Generating new report");
|
||||
// Start the generation pipeline
|
||||
const reportGeneration$ = forkJoin([this._ciphers$.pipe(take(1)), memberCiphers$]).pipe(
|
||||
switchMap(([ciphers, memberCiphers]) => {
|
||||
this.logService.debug("[RiskInsightsOrchestratorService] Analyzing password health");
|
||||
this._reportProgressSubject.next(ReportProgress.AnalyzingPasswords);
|
||||
return this._getCipherHealth(ciphers ?? [], memberCiphers);
|
||||
}),
|
||||
map((cipherHealthReports) => {
|
||||
this.logService.debug("[RiskInsightsOrchestratorService] Calculating risk scores");
|
||||
this._reportProgressSubject.next(ReportProgress.CalculatingRisks);
|
||||
return this.reportService.generateApplicationsReport(cipherHealthReports);
|
||||
}),
|
||||
tap(() => {
|
||||
this.logService.debug("[RiskInsightsOrchestratorService] Generating report data");
|
||||
this._reportProgressSubject.next(ReportProgress.GeneratingReport);
|
||||
}),
|
||||
switchMap(([ciphers, memberCiphers]) => this._getCipherHealth(ciphers ?? [], memberCiphers)),
|
||||
map((cipherHealthReports) =>
|
||||
this.reportService.generateApplicationsReport(cipherHealthReports),
|
||||
),
|
||||
withLatestFrom(this.rawReportData$),
|
||||
map(([report, previousReport]) => {
|
||||
// Update the application data
|
||||
@@ -680,6 +699,8 @@ export class RiskInsightsOrchestratorService {
|
||||
};
|
||||
}),
|
||||
switchMap(({ report, summary, applications, metrics }) => {
|
||||
this.logService.debug("[RiskInsightsOrchestratorService] Saving report");
|
||||
this._reportProgressSubject.next(ReportProgress.Saving);
|
||||
return this.reportService
|
||||
.saveRiskInsightsReport$(report, summary, applications, metrics, {
|
||||
organizationId,
|
||||
@@ -696,6 +717,10 @@ export class RiskInsightsOrchestratorService {
|
||||
);
|
||||
}),
|
||||
// Update the running state
|
||||
tap(() => {
|
||||
this.logService.debug("[RiskInsightsOrchestratorService] Report generation complete");
|
||||
this._reportProgressSubject.next(ReportProgress.Complete);
|
||||
}),
|
||||
map((mappedResult): ReportState => {
|
||||
const { id, report, summary, applications, contentEncryptionKey } = mappedResult;
|
||||
return {
|
||||
@@ -723,7 +748,9 @@ export class RiskInsightsOrchestratorService {
|
||||
error: null,
|
||||
data: null,
|
||||
}),
|
||||
);
|
||||
) as Observable<ReportState>;
|
||||
|
||||
return reportGeneration$;
|
||||
}
|
||||
|
||||
// Calculates the metrics for a report
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
DrawerType,
|
||||
RiskInsightsEnrichedData,
|
||||
ReportStatus,
|
||||
ReportProgress,
|
||||
ApplicationHealthReportDetail,
|
||||
OrganizationReportApplication,
|
||||
} from "../../models";
|
||||
@@ -38,6 +39,7 @@ export class RiskInsightsDataService {
|
||||
readonly isGeneratingReport$: Observable<boolean> = of(false);
|
||||
readonly criticalReportResults$: Observable<RiskInsightsEnrichedData | null> = of(null);
|
||||
readonly hasCiphers$: Observable<boolean | null> = of(null);
|
||||
readonly reportProgress$: Observable<ReportProgress | null> = of(null);
|
||||
|
||||
// New applications that need review (reviewedDate === null)
|
||||
readonly newApplications$: Observable<ApplicationHealthReportDetail[]> = of([]);
|
||||
@@ -62,6 +64,7 @@ export class RiskInsightsDataService {
|
||||
this.enrichedReportData$ = this.orchestrator.enrichedReportData$;
|
||||
this.criticalReportResults$ = this.orchestrator.criticalReportResults$;
|
||||
this.newApplications$ = this.orchestrator.newApplications$;
|
||||
this.reportProgress$ = this.orchestrator.reportProgress$;
|
||||
|
||||
this.hasCiphers$ = this.orchestrator.hasCiphers$.pipe(distinctUntilChanged());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user