@@ -2,12 +2,15 @@ import { CommonModule } from "@angular/common";
22import {
33 ChangeDetectionStrategy ,
44 Component ,
5+ computed ,
56 DestroyRef ,
67 Inject ,
78 inject ,
9+ Injector ,
10+ Signal ,
811 signal ,
912} from "@angular/core" ;
10- import { takeUntilDestroyed } from "@angular/core/rxjs-interop" ;
13+ import { takeUntilDestroyed , toSignal } from "@angular/core/rxjs-interop" ;
1114import { from , switchMap , take } from "rxjs" ;
1215
1316import {
@@ -17,7 +20,8 @@ import {
1720import { getUniqueMembers } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers" ;
1821import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service" ;
1922import { LogService } from "@bitwarden/common/platform/abstractions/log.service" ;
20- import { OrganizationId } from "@bitwarden/common/types/guid" ;
23+ import { CipherId , OrganizationId } from "@bitwarden/common/types/guid" ;
24+ import { SecurityTask , SecurityTaskStatus } from "@bitwarden/common/vault/tasks" ;
2125import {
2226 ButtonModule ,
2327 DIALOG_DATA ,
@@ -70,9 +74,9 @@ export type NewApplicationsDialogResultType =
7074 ( typeof NewApplicationsDialogResultType ) [ keyof typeof NewApplicationsDialogResultType ] ;
7175
7276@Component ( {
77+ changeDetection : ChangeDetectionStrategy . OnPush ,
7378 selector : "dirt-new-applications-dialog" ,
7479 templateUrl : "./new-applications-dialog.component.html" ,
75- changeDetection : ChangeDetectionStrategy . OnPush ,
7680 imports : [
7781 CommonModule ,
7882 ButtonModule ,
@@ -95,24 +99,63 @@ export class NewApplicationsDialogComponent {
9599 // Applications selected to save as critical applications
96100 protected readonly selectedApplications = signal < Set < string > > ( new Set ( ) ) ;
97101
98- // Assign tasks variables
99- readonly atRiskCriticalApplicationsCount = signal < number > ( 0 ) ;
100- readonly totalCriticalApplicationsCount = signal < number > ( 0 ) ;
101- readonly atRiskCriticalMembersCount = signal < number > ( 0 ) ;
102+ // Used to determine if there are unassigned at-risk cipher IDs
103+ private readonly _tasks ! : Signal < SecurityTask [ ] > ;
104+
105+ // Computed properties for selected applications
106+ protected readonly newCriticalApplications = computed ( ( ) => {
107+ return this . dialogParams . newApplications . filter ( ( newApp ) =>
108+ this . selectedApplications ( ) . has ( newApp . applicationName ) ,
109+ ) ;
110+ } ) ;
111+
112+ // New at risk critical applications
113+ protected readonly newAtRiskCriticalApplications = computed ( ( ) => {
114+ return this . newCriticalApplications ( ) . filter ( ( app ) => app . atRiskPasswordCount > 0 ) ;
115+ } ) ;
116+
117+ // Count of unique members with at-risk passwords in newly marked critical applications
118+ protected readonly atRiskCriticalMembersCount = computed ( ( ) => {
119+ return getUniqueMembers ( this . newCriticalApplications ( ) . flatMap ( ( x ) => x . atRiskMemberDetails ) )
120+ . length ;
121+ } ) ;
122+
123+ protected readonly newUnassignedAtRiskCipherIds = computed < CipherId [ ] > ( ( ) => {
124+ const newAtRiskCipherIds = this . newCriticalApplications ( ) . flatMap ( ( app ) => app . atRiskCipherIds ) ;
125+ const tasks = this . _tasks ( ) ;
126+
127+ if ( tasks . length === 0 ) {
128+ return newAtRiskCipherIds ;
129+ }
130+
131+ const inProgressTasks = tasks . filter ( ( task ) => task . status === SecurityTaskStatus . Pending ) ;
132+ const assignedIdSet = new Set ( inProgressTasks . map ( ( task ) => task . cipherId ) ) ;
133+ const unassignedIds = newAtRiskCipherIds . filter ( ( id ) => ! assignedIdSet . has ( id ) ) ;
134+ return unassignedIds ;
135+ } ) ;
136+
102137 readonly saving = signal < boolean > ( false ) ;
103138
104139 // Loading states
105140 protected readonly markingAsCritical = signal < boolean > ( false ) ;
106141
107142 constructor (
108143 @Inject ( DIALOG_DATA ) protected dialogParams : NewApplicationsDialogData ,
109- private dialogRef : DialogRef < NewApplicationsDialogResultType > ,
110144 private dataService : RiskInsightsDataService ,
111- private toastService : ToastService ,
145+ private dialogRef : DialogRef < NewApplicationsDialogResultType > ,
146+ private dialogService : DialogService ,
112147 private i18nService : I18nService ,
113- private accessIntelligenceSecurityTasksService : AccessIntelligenceSecurityTasksService ,
148+ private injector : Injector ,
114149 private logService : LogService ,
115- ) { }
150+ private securityTasksService : AccessIntelligenceSecurityTasksService ,
151+ private toastService : ToastService ,
152+ ) {
153+ // Setup the _tasks signal by manually passing in the injector
154+ this . _tasks = toSignal ( this . securityTasksService . tasks$ , {
155+ initialValue : [ ] ,
156+ injector : this . injector ,
157+ } ) ;
158+ }
116159
117160 /**
118161 * Opens the new applications dialog
@@ -170,53 +213,57 @@ export class NewApplicationsDialogComponent {
170213 } ) ;
171214 }
172215
173- handleMarkAsCritical ( ) {
174- if ( this . markingAsCritical ( ) || this . saving ( ) ) {
175- return ; // Prevent action if already processing
176- }
177- this . markingAsCritical . set ( true ) ;
178-
179- const onlyNewCriticalApplications = this . dialogParams . newApplications . filter ( ( newApp ) =>
180- this . selectedApplications ( ) . has ( newApp . applicationName ) ,
181- ) ;
182-
183- // Count only critical applications that have at-risk passwords
184- const atRiskCriticalApplicationsCount = onlyNewCriticalApplications . filter (
185- ( app ) => app . atRiskPasswordCount > 0 ,
186- ) . length ;
187- this . atRiskCriticalApplicationsCount . set ( atRiskCriticalApplicationsCount ) ;
188-
189- // Total number of selected critical applications
190- this . totalCriticalApplicationsCount . set ( onlyNewCriticalApplications . length ) ;
216+ // Checks if there are selected applications and proceeds to assign tasks
217+ async handleMarkAsCritical ( ) {
218+ if ( this . selectedApplications ( ) . size === 0 ) {
219+ const confirmed = await this . dialogService . openSimpleDialog ( {
220+ title : { key : "confirmNoSelectedCriticalApplicationsTitle" } ,
221+ content : { key : "confirmNoSelectedCriticalApplicationsDesc" } ,
222+ type : "warning" ,
223+ } ) ;
191224
192- const atRiskCriticalMembersCount = getUniqueMembers (
193- onlyNewCriticalApplications . flatMap ( ( x ) => x . atRiskMemberDetails ) ,
194- ) . length ;
195- this . atRiskCriticalMembersCount . set ( atRiskCriticalMembersCount ) ;
225+ if ( ! confirmed ) {
226+ return ;
227+ }
228+ }
196229
197- this . currentView . set ( DialogView . AssignTasks ) ;
198- this . markingAsCritical . set ( false ) ;
230+ // Skip the assign tasks view if there are no new unassigned at-risk cipher IDs
231+ if ( this . newUnassignedAtRiskCipherIds ( ) . length === 0 ) {
232+ this . handleAssignTasks ( ) ;
233+ } else {
234+ this . currentView . set ( DialogView . AssignTasks ) ;
235+ }
199236 }
200237
201- /**
202- * Handles the assign tasks button click
203- */
238+ // Saves the application review and assigns tasks for unassigned at-risk ciphers
204239 protected handleAssignTasks ( ) {
205240 if ( this . saving ( ) ) {
206241 return ; // Prevent double-click
207242 }
208243 this . saving . set ( true ) ;
209244
245+ const reviewedDate = new Date ( ) ;
246+ const updatedApplications = this . dialogParams . newApplications . map ( ( app ) => {
247+ const isCritical = this . selectedApplications ( ) . has ( app . applicationName ) ;
248+ return {
249+ applicationName : app . applicationName ,
250+ isCritical,
251+ reviewedDate,
252+ } ;
253+ } ) ;
254+
210255 // Save the application review dates and critical markings
211- this . dataService . criticalApplicationAtRiskCipherIds$
256+ this . dataService
257+ . saveApplicationReviewStatus ( updatedApplications )
212258 . pipe (
213259 takeUntilDestroyed ( this . destroyRef ) , // Satisfy eslint rule
214- take ( 1 ) , // Handle unsubscribe for one off operation
215- switchMap ( ( criticalApplicationAtRiskCipherIds ) => {
260+ take ( 1 ) ,
261+ switchMap ( ( ) => {
262+ // Assign password change tasks for unassigned at-risk ciphers for critical applications
216263 return from (
217- this . accessIntelligenceSecurityTasksService . requestPasswordChangeForCriticalApplications (
264+ this . securityTasksService . requestPasswordChangeForCriticalApplications (
218265 this . dialogParams . organizationId ,
219- criticalApplicationAtRiskCipherIds ,
266+ this . newUnassignedAtRiskCipherIds ( ) ,
220267 ) ,
221268 ) ;
222269 } ) ,
0 commit comments