diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts index 60b53f7405d..c1a00731100 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, DestroyRef, - Injector, OnInit, Signal, computed, @@ -11,7 +10,7 @@ import { input, signal, } from "@angular/core"; -import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; +import { toSignal } from "@angular/core/rxjs-interop"; import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -59,6 +58,9 @@ export class PasswordChangeMetricComponent implements OnInit { private readonly _tasks: Signal = signal([]); private readonly _atRiskCipherIds: Signal = signal([]); private readonly _hasCriticalApplications: Signal = signal(false); + private readonly _reportGeneratedAt: Signal = signal( + undefined, + ); // Computed properties readonly tasksCount = computed(() => this._tasks().length); @@ -80,8 +82,24 @@ export class PasswordChangeMetricComponent implements OnInit { } const inProgressTasks = tasks.filter((task) => task.status === SecurityTaskStatus.Pending); - const assignedIdSet = new Set(inProgressTasks.map((task) => task.cipherId)); - const unassignedIds = atRiskIds.filter((id) => !assignedIdSet.has(id)); + const inProgressTaskIds = new Set(inProgressTasks.map((task) => task.cipherId)); + + const reportGeneratedAt = this._reportGeneratedAt(); + const completedTasksAfterReportGeneration = reportGeneratedAt + ? tasks.filter( + (task) => + task.status === SecurityTaskStatus.Completed && + new Date(task.revisionDate) >= reportGeneratedAt, + ) + : []; + const completedTaskIds = new Set( + completedTasksAfterReportGeneration.map((task) => task.cipherId), + ); + + // find cipher ids from last report that do not have a corresponding in progress task (awaiting password reset) OR completed task + const unassignedIds = atRiskIds.filter( + (id) => !inProgressTaskIds.has(id) && !completedTaskIds.has(id), + ); return unassignedIds.length; }); @@ -109,36 +127,26 @@ export class PasswordChangeMetricComponent implements OnInit { constructor( private allActivitiesService: AllActivitiesService, private i18nService: I18nService, - private injector: Injector, private riskInsightsDataService: RiskInsightsDataService, protected securityTasksService: AccessIntelligenceSecurityTasksService, private toastService: ToastService, ) { - // Setup the _tasks signal by manually passing in the injector - this._tasks = toSignal(this.securityTasksService.tasks$, { - initialValue: [], - injector: this.injector, - }); - // Setup the _atRiskCipherIds signal by manually passing in the injector + this._tasks = toSignal(this.securityTasksService.tasks$, { initialValue: [] }); this._atRiskCipherIds = toSignal( this.riskInsightsDataService.criticalApplicationAtRiskCipherIds$, - { - initialValue: [], - injector: this.injector, - }, + { initialValue: [] }, ); - this._hasCriticalApplications = toSignal( this.riskInsightsDataService.criticalReportResults$.pipe( - takeUntilDestroyed(this.destroyRef), map((report) => { return report != null && (report.reportData?.length ?? 0) > 0; }), ), - { - initialValue: false, - injector: this.injector, - }, + { initialValue: false }, + ); + this._reportGeneratedAt = toSignal( + this.riskInsightsDataService.enrichedReportData$.pipe(map((report) => report?.creationDate)), + { initialValue: undefined }, ); effect(() => {