Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6d5eb3d
refactor(risk-insights-data.service): unify drawer state management wโ€ฆ
AlexRubik Sep 23, 2025
fa809a4
risk-insights.component: add special case drawer state sync in component
AlexRubik Sep 23, 2025
5a208d7
risk-insights.component.html: replace drawer template with unified obโ€ฆ
AlexRubik Sep 24, 2025
4ddbbfe
all-applications.component.html: replace drawer state with reactive oโ€ฆ
AlexRubik Sep 24, 2025
34df31e
critical-applications.component.html: replace drawer state with reactโ€ฆ
AlexRubik Sep 24, 2025
85aad1c
all-applications.component.ts: remove deprecated drawer state functions
AlexRubik Sep 24, 2025
6a063cf
critical-applications.component.ts: remove deprecated drawer state fuโ€ฆ
AlexRubik Sep 24, 2025
236cbb6
app-table-row-scrollable.component.html: replace drawer function callโ€ฆ
AlexRubik Sep 24, 2025
d31afff
Merge branch 'main' into dirt/pm-26031/drawer-service
AlexRubik Sep 24, 2025
a004a31
fix(risk-insights-data.service.ts): restore drawer toggle behavior inโ€ฆ
AlexRubik Sep 24, 2025
4fa3429
revert to drawer state functions to maintain scope of task
AlexRubik Sep 25, 2025
31d363d
fix(risk-insights-data.service.ts): restore boolean isActiveDrawerTypโ€ฆ
AlexRubik Sep 26, 2025
7df3566
refactor(risk-insights-data.service.ts): use destructuring in drawer โ€ฆ
AlexRubik Sep 26, 2025
aec10ad
refactor(all-applications.component.html): optimize single subscriptiโ€ฆ
AlexRubik Sep 26, 2025
e6c7761
refactor(critical-applications.component.html): optimize single subscโ€ฆ
AlexRubik Sep 26, 2025
69593ac
refactor(risk-insights.component.html): use boolean drawer type functโ€ฆ
AlexRubik Sep 26, 2025
574447e
Merge branch 'main' into dirt/pm-26031/drawer-service
AlexRubik Sep 26, 2025
2f8232e
fix(browser-system-notification.service.ts): restore eslint disable cโ€ฆ
AlexRubik Sep 26, 2025
3e28619
Merge branch 'main' into dirt/pm-26031/drawer-service
ttalty Sep 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
import { finalize, switchMap, withLatestFrom } from "rxjs/operators";
import { finalize, switchMap, withLatestFrom, map } from "rxjs/operators";

import {
getOrganizationById,
Expand All @@ -14,6 +14,7 @@ import {
AtRiskApplicationDetail,
AtRiskMemberDetail,
DrawerType,
DrawerDetails,
ApplicationHealthReportDetail,
ApplicationHealthReportDetailEnriched,
} from "../models/report-models";
Expand Down Expand Up @@ -53,12 +54,17 @@ export class RiskInsightsDataService {
private errorSubject = new BehaviorSubject<string | null>(null);
error$ = this.errorSubject.asObservable();

openDrawer = false;
drawerInvokerId: string = "";
activeDrawerType: DrawerType = DrawerType.None;
atRiskMemberDetails: AtRiskMemberDetail[] = [];
appAtRiskMembers: AppAtRiskMembersDialogParams | null = null;
atRiskAppDetails: AtRiskApplicationDetail[] | null = null;
// ------------------------- Drawer Variables ----------------
// Drawer variables unified into a single BehaviorSubject
private drawerDetailsSubject = new BehaviorSubject<DrawerDetails>({
open: false,
invokerId: "",
activeDrawerType: DrawerType.None,
atRiskMemberDetails: [],
appAtRiskMembers: null,
atRiskAppDetails: null,
});
drawerDetails$ = this.drawerDetailsSubject.asObservable();

constructor(
private accountService: AccountService,
Expand Down Expand Up @@ -178,56 +184,96 @@ export class RiskInsightsDataService {
}

// ------------------------------- Drawer management methods -------------------------------
// ------------------------- Drawer functions -----------------------------

isActiveDrawerType$ = (drawerType: DrawerType): Observable<boolean> => {
return this.drawerDetails$.pipe(map((details) => details.activeDrawerType === drawerType));
};
isActiveDrawerType = (drawerType: DrawerType): boolean => {
return this.activeDrawerType === drawerType;
return this.drawerDetailsSubject.value.activeDrawerType === drawerType;
};

isDrawerOpenForInvoker$ = (applicationName: string) => {
return this.drawerDetails$.pipe(map((details) => details.invokerId === applicationName));
};
isDrawerOpenForInvoker = (applicationName: string): boolean => {
return this.drawerDetailsSubject.value.invokerId === applicationName;
};

closeDrawer = (): void => {
this.drawerDetailsSubject.next({
open: false,
invokerId: "",
activeDrawerType: DrawerType.None,
atRiskMemberDetails: [],
appAtRiskMembers: null,
atRiskAppDetails: null,
});
};

setDrawerForOrgAtRiskMembers = (
atRiskMemberDetails: AtRiskMemberDetail[],
invokerId: string = "",
): void => {
this.resetDrawer(DrawerType.OrgAtRiskMembers);
this.activeDrawerType = DrawerType.OrgAtRiskMembers;
this.drawerInvokerId = invokerId;
this.atRiskMemberDetails = atRiskMemberDetails;
this.openDrawer = !this.openDrawer;
const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
const shouldClose =
open && activeDrawerType === DrawerType.OrgAtRiskMembers && currentInvokerId === invokerId;

if (shouldClose) {
this.closeDrawer();
} else {
this.drawerDetailsSubject.next({
open: true,
invokerId,
activeDrawerType: DrawerType.OrgAtRiskMembers,
atRiskMemberDetails,
appAtRiskMembers: null,
atRiskAppDetails: null,
});
}
};

setDrawerForAppAtRiskMembers = (
atRiskMembersDialogParams: AppAtRiskMembersDialogParams,
invokerId: string = "",
): void => {
this.resetDrawer(DrawerType.None);
this.activeDrawerType = DrawerType.AppAtRiskMembers;
this.drawerInvokerId = invokerId;
this.appAtRiskMembers = atRiskMembersDialogParams;
this.openDrawer = !this.openDrawer;
const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
const shouldClose =
open && activeDrawerType === DrawerType.AppAtRiskMembers && currentInvokerId === invokerId;

if (shouldClose) {
this.closeDrawer();
} else {
this.drawerDetailsSubject.next({
open: true,
invokerId,
activeDrawerType: DrawerType.AppAtRiskMembers,
atRiskMemberDetails: [],
appAtRiskMembers: atRiskMembersDialogParams,
atRiskAppDetails: null,
});
}
};

setDrawerForOrgAtRiskApps = (
atRiskApps: AtRiskApplicationDetail[],
invokerId: string = "",
): void => {
this.resetDrawer(DrawerType.OrgAtRiskApps);
this.activeDrawerType = DrawerType.OrgAtRiskApps;
this.drawerInvokerId = invokerId;
this.atRiskAppDetails = atRiskApps;
this.openDrawer = !this.openDrawer;
};
const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
const shouldClose =
open && activeDrawerType === DrawerType.OrgAtRiskApps && currentInvokerId === invokerId;

closeDrawer = (): void => {
this.resetDrawer(DrawerType.None);
};

private resetDrawer = (drawerType: DrawerType): void => {
if (this.activeDrawerType !== drawerType) {
this.openDrawer = false;
if (shouldClose) {
this.closeDrawer();
} else {
this.drawerDetailsSubject.next({
open: true,
invokerId,
activeDrawerType: DrawerType.OrgAtRiskApps,
atRiskMemberDetails: [],
appAtRiskMembers: null,
atRiskAppDetails: atRiskApps,
});
}

this.activeDrawerType = DrawerType.None;
this.atRiskMemberDetails = [];
this.appAtRiskMembers = null;
this.atRiskAppDetails = null;
this.drawerInvokerId = "";
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,46 @@ <h2 class="tw-font-semibold tw-mt-4">
</div>
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!(isLoading$ | async) && dataSource.data.length">
<h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2>
<div class="tw-flex tw-gap-6">
<button
type="button"
class="tw-flex-1"
tabindex="0"
(click)="showOrgAtRiskMembers('allAppsOrgAtRiskMembers')"
>
<dirt-card
#allAppsOrgAtRiskMembers
class="tw-w-full"
[ngClass]="{
'tw-bg-primary-100': dataService.drawerInvokerId === 'allAppsOrgAtRiskMembers',
}"
[title]="'atRiskMembers' | i18n"
[value]="applicationSummary.totalAtRiskMemberCount"
[maxValue]="applicationSummary.totalMemberCount"
@if (dataService.drawerDetails$ | async; as drawerDetails) {
<div class="tw-flex tw-gap-6">
<button
type="button"
class="tw-flex-1"
tabindex="0"
(click)="showOrgAtRiskMembers('allAppsOrgAtRiskMembers')"
>
</dirt-card>
</button>
<button
type="button"
class="tw-flex-1"
tabindex="0"
(click)="showOrgAtRiskApps('allAppsOrgAtRiskApplications')"
>
<dirt-card
#allAppsOrgAtRiskApplications
class="tw-w-full"
[ngClass]="{
'tw-bg-primary-100': dataService.drawerInvokerId === 'allAppsOrgAtRiskApplications',
}"
[title]="'atRiskApplications' | i18n"
[value]="applicationSummary.totalAtRiskApplicationCount"
[maxValue]="applicationSummary.totalApplicationCount"
<dirt-card
#allAppsOrgAtRiskMembers
class="tw-w-full"
[ngClass]="{
'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers',
}"
[title]="'atRiskMembers' | i18n"
[value]="applicationSummary.totalAtRiskMemberCount"
[maxValue]="applicationSummary.totalMemberCount"
>
</dirt-card>
</button>
<button
type="button"
class="tw-flex-1"
tabindex="0"
(click)="showOrgAtRiskApps('allAppsOrgAtRiskApplications')"
>
</dirt-card>
</button>
</div>
<dirt-card
#allAppsOrgAtRiskApplications
class="tw-w-full"
[ngClass]="{
'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications',
}"
[title]="'atRiskApplications' | i18n"
[value]="applicationSummary.totalAtRiskApplicationCount"
[maxValue]="applicationSummary.totalApplicationCount"
>
</dirt-card>
</button>
</div>
}
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
<bit-search
[placeholder]="'searchApps' | i18n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ import {
LEGACY_ApplicationHealthReportDetailWithCriticalFlag,
LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health";
import {
ApplicationHealthReportDetail,
OrganizationReportSummary,
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
import { OrganizationReportSummary } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
import { RiskInsightsEncryptionService } from "@bitwarden/bit-common/dirt/reports/risk-insights/services/risk-insights-encryption.service";
import {
getOrganizationById,
Expand Down Expand Up @@ -193,10 +190,6 @@ export class AllApplicationsComponent implements OnInit {
}
};

trackByFunction(_: number, item: ApplicationHealthReportDetail) {
return item.applicationName;
}

showAppAtRiskMembers = async (applicationName: string) => {
const info = {
members:
Expand Down Expand Up @@ -226,9 +219,9 @@ export class AllApplicationsComponent implements OnInit {
}
};

getSelectedUrls = () => Array.from(this.selectedUrls);

isDrawerOpenForTableRow = (applicationName: string): boolean => {
return this.dataService.drawerInvokerId === applicationName;
// Note: This function will be replaced by PR #16523 with openApplication binding
// Using private access to BehaviorSubject value for backward compatibility
return (this.dataService as any).drawerDetailsSubject?.value?.invokerId === applicationName;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,44 +43,46 @@ <h2 bitTypography="h2">{{ "criticalApplications" | i18n }}</h2>
}}
</button>
</div>
<div class="tw-flex tw-gap-6">
<button
type="button"
class="tw-flex-1"
tabindex="0"
(click)="showOrgAtRiskMembers('criticalAppsAtRiskMembers')"
>
<dirt-card
#criticalAppsAtRiskMembers
class="tw-w-full"
[ngClass]="{
'tw-bg-primary-100': dataService.drawerInvokerId === 'criticalAppsAtRiskMembers',
}"
[title]="'atRiskMembers' | i18n"
[value]="applicationSummary.totalAtRiskMemberCount"
[maxValue]="applicationSummary.totalMemberCount"
@if (dataService.drawerDetails$ | async; as drawerDetails) {
<div class="tw-flex tw-gap-6">
<button
type="button"
class="tw-flex-1"
tabindex="0"
(click)="showOrgAtRiskMembers('criticalAppsAtRiskMembers')"
>
</dirt-card>
</button>
<button
type="button"
class="tw-flex-1"
tabindex="0"
(click)="showOrgAtRiskApps('criticalAppsAtRiskApplications')"
>
<dirt-card
#criticalAppsAtRiskApplications
class="tw-w-full"
[ngClass]="{
'tw-bg-primary-100': dataService.drawerInvokerId === 'criticalAppsAtRiskApplications',
}"
[title]="'atRiskApplications' | i18n"
[value]="applicationSummary.totalAtRiskApplicationCount"
[maxValue]="applicationSummary.totalApplicationCount"
<dirt-card
#criticalAppsAtRiskMembers
class="tw-w-full"
[ngClass]="{
'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskMembers',
}"
[title]="'atRiskMembers' | i18n"
[value]="applicationSummary.totalAtRiskMemberCount"
[maxValue]="applicationSummary.totalMemberCount"
>
</dirt-card>
</button>
<button
type="button"
class="tw-flex-1"
tabindex="0"
(click)="showOrgAtRiskApps('criticalAppsAtRiskApplications')"
>
</dirt-card>
</button>
</div>
<dirt-card
#criticalAppsAtRiskApplications
class="tw-w-full"
[ngClass]="{
'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskApplications',
}"
[title]="'atRiskApplications' | i18n"
[value]="applicationSummary.totalAtRiskApplicationCount"
[maxValue]="applicationSummary.totalApplicationCount"
>
</dirt-card>
</button>
</div>
}
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
<bit-search
[placeholder]="'searchApps' | i18n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,9 @@ export class CriticalApplicationsComponent implements OnInit {
this.dataService.setDrawerForOrgAtRiskApps(data, invokerId);
};

trackByFunction(_: number, item: LEGACY_ApplicationHealthReportDetailWithCriticalFlag) {
return item.applicationName;
}
isDrawerOpenForTableRow = (applicationName: string) => {
return this.dataService.drawerInvokerId === applicationName;
// Note: This function will be replaced by PR #16523 with openApplication binding
// Using private access to BehaviorSubject value for backward compatibility
return (this.dataService as any).drawerDetailsSubject?.value?.invokerId === applicationName;
};
}
Loading
Loading