1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 22:33:35 +00:00

[PM-26031] Drawer Service State Refactoring (#16580)

* refactor(risk-insights-data.service): unify drawer state management with BehaviorSubject

- Replace individual drawer properties with unified drawerDetailsSubject
- Add reactive Observable getters for drawer state checking
- Update all drawer methods to use centralized state management

* risk-insights.component: add special case drawer state sync in component

- Add private _isDrawerOpen property for internal state tracking
- Subscribe to drawerDetails$ changes with takeUntilDestroyed cleanup
- Implement getter/setter for isDrawerOpen to sync component <-> service
- Enable two-way binding while maintaining reactive patterns

* risk-insights.component.html: replace drawer template with unified observable patterns

- Replace dataService.openDrawer with isDrawerOpen special case getter
- Wrap drawer in @if block with drawerDetails$ | async for single subscription
- Update isActiveDrawerType() calls to reactive isActiveDrawerType$() | async
- Replace direct property access with unified drawerDetails object
- Use modern @if control flow syntax for better performance

* all-applications.component.html: replace drawer state with reactive observable patterns

- Replace dataService.drawerInvokerId with drawerDetails$ | async in card highlighting
- Update app-table-row-scrollable input from isDrawerIsOpenForThisRecord function to openApplication string

* critical-applications.component.html: replace drawer state with reactive observable patterns

- Replace dataService.drawerInvokerId with drawerDetails$ | async in card highlighting
- Update table component binding from isDrawerIsOpenForThisRecord to openApplication
- Use reactive drawer state checking for consistent behavior with all-applications

* all-applications.component.ts: remove deprecated drawer state functions

- Remove unused trackByFunction that's no longer needed in template
- Remove getSelectedUrls function that's not used anywhere
- Remove isDrawerOpenForTableRow replaced by reactive openApplication binding
- Clean up unused ApplicationHealthReportDetail import
- Simplifies component interface following reactive pattern migration

* critical-applications.component.ts: remove deprecated drawer state functions

- Remove unused trackByFunction that's no longer needed in template
- Remove isDrawerOpenForTableRow replaced by reactive openApplication binding

* app-table-row-scrollable.component.html: replace drawer function calls with string comparison

- Replace isDrawerIsOpenForThisRecord(row.applicationName) with row.applicationName === openApplication
- Use direct string comparison instead of function calls for better performance
- Matches updated component input from function to string property
- Simplifies template logic following reactive pattern migration

* fix(risk-insights-data.service.ts): restore drawer toggle behavior in setter methods

- Add toggle logic to check if same drawer type and invoker are already open
- Close drawer when clicking same button twice (preserves original UX)
- Switch drawer content when clicking different button
- Maintains reactive patterns while restoring expected behavior

* revert to drawer state functions to maintain scope of task
- the logic replacing these functions will be in pr16523

* fix(risk-insights-data.service.ts): restore boolean isActiveDrawerType function per review feedback

- Keep original isActiveDrawerType() as boolean function using drawerDetailsSubject.value
- Maintain isActiveDrawerType$() as Observable version for reactive templates
- Apply same pattern to isDrawerOpenForInvoker() for consistency
- Addresses review feedback to preserve existing function signatures

* refactor(risk-insights-data.service.ts): use destructuring in drawer setter methods per review feedback

* refactor(all-applications.component.html): optimize single subscription for drawer state per review feedback

* refactor(critical-applications.component.html): optimize single subscription for drawer state per review feedback

* refactor(risk-insights.component.html): use boolean drawer type functions per review feedback

* fix(browser-system-notification.service.ts): restore eslint disable comment removed by prettier

---------

Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com>
This commit is contained in:
Alex
2025-09-26 16:25:16 -04:00
committed by GitHub
parent 8ba22f3080
commit 979e370235
7 changed files with 280 additions and 208 deletions

View File

@@ -1,5 +1,5 @@
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
import { finalize, switchMap, withLatestFrom } from "rxjs/operators"; import { finalize, switchMap, withLatestFrom, map } from "rxjs/operators";
import { import {
getOrganizationById, getOrganizationById,
@@ -14,6 +14,7 @@ import {
AtRiskApplicationDetail, AtRiskApplicationDetail,
AtRiskMemberDetail, AtRiskMemberDetail,
DrawerType, DrawerType,
DrawerDetails,
ApplicationHealthReportDetail, ApplicationHealthReportDetail,
ApplicationHealthReportDetailEnriched, ApplicationHealthReportDetailEnriched,
} from "../models/report-models"; } from "../models/report-models";
@@ -53,12 +54,17 @@ export class RiskInsightsDataService {
private errorSubject = new BehaviorSubject<string | null>(null); private errorSubject = new BehaviorSubject<string | null>(null);
error$ = this.errorSubject.asObservable(); error$ = this.errorSubject.asObservable();
openDrawer = false; // ------------------------- Drawer Variables ----------------
drawerInvokerId: string = ""; // Drawer variables unified into a single BehaviorSubject
activeDrawerType: DrawerType = DrawerType.None; private drawerDetailsSubject = new BehaviorSubject<DrawerDetails>({
atRiskMemberDetails: AtRiskMemberDetail[] = []; open: false,
appAtRiskMembers: AppAtRiskMembersDialogParams | null = null; invokerId: "",
atRiskAppDetails: AtRiskApplicationDetail[] | null = null; activeDrawerType: DrawerType.None,
atRiskMemberDetails: [],
appAtRiskMembers: null,
atRiskAppDetails: null,
});
drawerDetails$ = this.drawerDetailsSubject.asObservable();
constructor( constructor(
private accountService: AccountService, private accountService: AccountService,
@@ -178,56 +184,96 @@ export class RiskInsightsDataService {
} }
// ------------------------------- Drawer management methods ------------------------------- // ------------------------------- Drawer management methods -------------------------------
// ------------------------- Drawer functions -----------------------------
isActiveDrawerType$ = (drawerType: DrawerType): Observable<boolean> => {
return this.drawerDetails$.pipe(map((details) => details.activeDrawerType === drawerType));
};
isActiveDrawerType = (drawerType: DrawerType): boolean => { 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 = ( setDrawerForOrgAtRiskMembers = (
atRiskMemberDetails: AtRiskMemberDetail[], atRiskMemberDetails: AtRiskMemberDetail[],
invokerId: string = "", invokerId: string = "",
): void => { ): void => {
this.resetDrawer(DrawerType.OrgAtRiskMembers); const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
this.activeDrawerType = DrawerType.OrgAtRiskMembers; const shouldClose =
this.drawerInvokerId = invokerId; open && activeDrawerType === DrawerType.OrgAtRiskMembers && currentInvokerId === invokerId;
this.atRiskMemberDetails = atRiskMemberDetails;
this.openDrawer = !this.openDrawer; if (shouldClose) {
this.closeDrawer();
} else {
this.drawerDetailsSubject.next({
open: true,
invokerId,
activeDrawerType: DrawerType.OrgAtRiskMembers,
atRiskMemberDetails,
appAtRiskMembers: null,
atRiskAppDetails: null,
});
}
}; };
setDrawerForAppAtRiskMembers = ( setDrawerForAppAtRiskMembers = (
atRiskMembersDialogParams: AppAtRiskMembersDialogParams, atRiskMembersDialogParams: AppAtRiskMembersDialogParams,
invokerId: string = "", invokerId: string = "",
): void => { ): void => {
this.resetDrawer(DrawerType.None); const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
this.activeDrawerType = DrawerType.AppAtRiskMembers; const shouldClose =
this.drawerInvokerId = invokerId; open && activeDrawerType === DrawerType.AppAtRiskMembers && currentInvokerId === invokerId;
this.appAtRiskMembers = atRiskMembersDialogParams;
this.openDrawer = !this.openDrawer; if (shouldClose) {
this.closeDrawer();
} else {
this.drawerDetailsSubject.next({
open: true,
invokerId,
activeDrawerType: DrawerType.AppAtRiskMembers,
atRiskMemberDetails: [],
appAtRiskMembers: atRiskMembersDialogParams,
atRiskAppDetails: null,
});
}
}; };
setDrawerForOrgAtRiskApps = ( setDrawerForOrgAtRiskApps = (
atRiskApps: AtRiskApplicationDetail[], atRiskApps: AtRiskApplicationDetail[],
invokerId: string = "", invokerId: string = "",
): void => { ): void => {
this.resetDrawer(DrawerType.OrgAtRiskApps); const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value;
this.activeDrawerType = DrawerType.OrgAtRiskApps; const shouldClose =
this.drawerInvokerId = invokerId; open && activeDrawerType === DrawerType.OrgAtRiskApps && currentInvokerId === invokerId;
this.atRiskAppDetails = atRiskApps;
this.openDrawer = !this.openDrawer;
};
closeDrawer = (): void => { if (shouldClose) {
this.resetDrawer(DrawerType.None); this.closeDrawer();
}; } else {
this.drawerDetailsSubject.next({
private resetDrawer = (drawerType: DrawerType): void => { open: true,
if (this.activeDrawerType !== drawerType) { invokerId,
this.openDrawer = false; activeDrawerType: DrawerType.OrgAtRiskApps,
atRiskMemberDetails: [],
appAtRiskMembers: null,
atRiskAppDetails: atRiskApps,
});
} }
this.activeDrawerType = DrawerType.None;
this.atRiskMemberDetails = [];
this.appAtRiskMembers = null;
this.atRiskAppDetails = null;
this.drawerInvokerId = "";
}; };
} }

View File

@@ -25,6 +25,7 @@
</div> </div>
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!(isLoading$ | async) && dataSource.data.length"> <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> <h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2>
@if (dataService.drawerDetails$ | async; as drawerDetails) {
<div class="tw-flex tw-gap-6"> <div class="tw-flex tw-gap-6">
<button <button
type="button" type="button"
@@ -36,7 +37,7 @@
#allAppsOrgAtRiskMembers #allAppsOrgAtRiskMembers
class="tw-w-full" class="tw-w-full"
[ngClass]="{ [ngClass]="{
'tw-bg-primary-100': dataService.drawerInvokerId === 'allAppsOrgAtRiskMembers', 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers',
}" }"
[title]="'atRiskMembers' | i18n" [title]="'atRiskMembers' | i18n"
[value]="applicationSummary.totalAtRiskMemberCount" [value]="applicationSummary.totalAtRiskMemberCount"
@@ -54,7 +55,7 @@
#allAppsOrgAtRiskApplications #allAppsOrgAtRiskApplications
class="tw-w-full" class="tw-w-full"
[ngClass]="{ [ngClass]="{
'tw-bg-primary-100': dataService.drawerInvokerId === 'allAppsOrgAtRiskApplications', 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications',
}" }"
[title]="'atRiskApplications' | i18n" [title]="'atRiskApplications' | i18n"
[value]="applicationSummary.totalAtRiskApplicationCount" [value]="applicationSummary.totalAtRiskApplicationCount"
@@ -63,6 +64,7 @@
</dirt-card> </dirt-card>
</button> </button>
</div> </div>
}
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4"> <div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
<bit-search <bit-search
[placeholder]="'searchApps' | i18n" [placeholder]="'searchApps' | i18n"

View File

@@ -14,10 +14,7 @@ import {
LEGACY_ApplicationHealthReportDetailWithCriticalFlag, LEGACY_ApplicationHealthReportDetailWithCriticalFlag,
LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher, LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health";
import { import { OrganizationReportSummary } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
ApplicationHealthReportDetail,
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 { RiskInsightsEncryptionService } from "@bitwarden/bit-common/dirt/reports/risk-insights/services/risk-insights-encryption.service";
import { import {
getOrganizationById, getOrganizationById,
@@ -193,10 +190,6 @@ export class AllApplicationsComponent implements OnInit {
} }
}; };
trackByFunction(_: number, item: ApplicationHealthReportDetail) {
return item.applicationName;
}
showAppAtRiskMembers = async (applicationName: string) => { showAppAtRiskMembers = async (applicationName: string) => {
const info = { const info = {
members: members:
@@ -226,9 +219,9 @@ export class AllApplicationsComponent implements OnInit {
} }
}; };
getSelectedUrls = () => Array.from(this.selectedUrls);
isDrawerOpenForTableRow = (applicationName: string): boolean => { 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;
}; };
} }

View File

@@ -43,6 +43,7 @@
}} }}
</button> </button>
</div> </div>
@if (dataService.drawerDetails$ | async; as drawerDetails) {
<div class="tw-flex tw-gap-6"> <div class="tw-flex tw-gap-6">
<button <button
type="button" type="button"
@@ -54,7 +55,7 @@
#criticalAppsAtRiskMembers #criticalAppsAtRiskMembers
class="tw-w-full" class="tw-w-full"
[ngClass]="{ [ngClass]="{
'tw-bg-primary-100': dataService.drawerInvokerId === 'criticalAppsAtRiskMembers', 'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskMembers',
}" }"
[title]="'atRiskMembers' | i18n" [title]="'atRiskMembers' | i18n"
[value]="applicationSummary.totalAtRiskMemberCount" [value]="applicationSummary.totalAtRiskMemberCount"
@@ -72,7 +73,7 @@
#criticalAppsAtRiskApplications #criticalAppsAtRiskApplications
class="tw-w-full" class="tw-w-full"
[ngClass]="{ [ngClass]="{
'tw-bg-primary-100': dataService.drawerInvokerId === 'criticalAppsAtRiskApplications', 'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskApplications',
}" }"
[title]="'atRiskApplications' | i18n" [title]="'atRiskApplications' | i18n"
[value]="applicationSummary.totalAtRiskApplicationCount" [value]="applicationSummary.totalAtRiskApplicationCount"
@@ -81,6 +82,7 @@
</dirt-card> </dirt-card>
</button> </button>
</div> </div>
}
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4"> <div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
<bit-search <bit-search
[placeholder]="'searchApps' | i18n" [placeholder]="'searchApps' | i18n"

View File

@@ -202,10 +202,9 @@ export class CriticalApplicationsComponent implements OnInit {
this.dataService.setDrawerForOrgAtRiskApps(data, invokerId); this.dataService.setDrawerForOrgAtRiskApps(data, invokerId);
}; };
trackByFunction(_: number, item: LEGACY_ApplicationHealthReportDetailWithCriticalFlag) {
return item.applicationName;
}
isDrawerOpenForTableRow = (applicationName: string) => { 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;
}; };
} }

View File

@@ -53,31 +53,28 @@
</bit-tab> </bit-tab>
</bit-tab-group> </bit-tab-group>
<bit-drawer @if (dataService.drawerDetails$ | async; as drawerDetails) {
style="width: 30%" <bit-drawer style="width: 30%" [(open)]="isDrawerOpen" (openChange)="dataService.closeDrawer()">
[(open)]="dataService.openDrawer"
(openChange)="dataService.closeDrawer()"
>
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.OrgAtRiskMembers)"> <ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.OrgAtRiskMembers)">
<bit-drawer-header <bit-drawer-header
title="{{ 'atRiskMembersWithCount' | i18n: dataService.atRiskMemberDetails.length }}" title="{{ 'atRiskMembersWithCount' | i18n: drawerDetails.atRiskMemberDetails.length }}"
> >
</bit-drawer-header> </bit-drawer-header>
<bit-drawer-body> <bit-drawer-body>
<span bitTypography="body1" class="tw-text-muted tw-text-sm">{{ <span bitTypography="body1" class="tw-text-muted tw-text-sm">{{
(dataService.atRiskMemberDetails.length > 0 (drawerDetails.atRiskMemberDetails.length > 0
? "atRiskMembersDescription" ? "atRiskMembersDescription"
: "atRiskMembersDescriptionNone" : "atRiskMembersDescriptionNone"
) | i18n ) | i18n
}}</span> }}</span>
<ng-container *ngIf="dataService.atRiskMemberDetails.length > 0"> <ng-container *ngIf="drawerDetails.atRiskMemberDetails.length > 0">
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted"> <div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
<div bitTypography="body2" class="tw-text-sm tw-font-bold">{{ "email" | i18n }}</div> <div bitTypography="body2" class="tw-text-sm tw-font-bold">{{ "email" | i18n }}</div>
<div bitTypography="body2" class="tw-text-sm tw-font-bold"> <div bitTypography="body2" class="tw-text-sm tw-font-bold">
{{ "atRiskPasswords" | i18n }} {{ "atRiskPasswords" | i18n }}
</div> </div>
</div> </div>
<ng-container *ngFor="let member of dataService.atRiskMemberDetails"> <ng-container *ngFor="let member of drawerDetails.atRiskMemberDetails">
<div class="tw-flex tw-justify-between tw-mt-2"> <div class="tw-flex tw-justify-between tw-mt-2">
<div>{{ member.email }}</div> <div>{{ member.email }}</div>
<div>{{ member.atRiskPasswordCount }}</div> <div>{{ member.atRiskPasswordCount }}</div>
@@ -87,43 +84,43 @@
</bit-drawer-body> </bit-drawer-body>
</ng-container> </ng-container>
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.AppAtRiskMembers)"> @if (dataService.isActiveDrawerType(drawerTypes.AppAtRiskMembers)) {
<bit-drawer-header title="{{ dataService.appAtRiskMembers.applicationName }}"> <bit-drawer-header title="{{ drawerDetails.appAtRiskMembers.applicationName }}">
</bit-drawer-header> </bit-drawer-header>
<bit-drawer-body> <bit-drawer-body>
<div bitTypography="body1" class="tw-mb-2"> <div bitTypography="body1" class="tw-mb-2">
{{ "atRiskMembersWithCount" | i18n: dataService.appAtRiskMembers.members.length }} {{ "atRiskMembersWithCount" | i18n: drawerDetails.appAtRiskMembers.members.length }}
</div> </div>
<div bitTypography="body1" class="tw-text-muted tw-text-sm tw-mb-2"> <div bitTypography="body1" class="tw-text-muted tw-text-sm tw-mb-2">
{{ {{
(dataService.appAtRiskMembers.members.length > 0 (drawerDetails.appAtRiskMembers.members.length > 0
? "atRiskMembersDescriptionWithApp" ? "atRiskMembersDescriptionWithApp"
: "atRiskMembersDescriptionWithAppNone" : "atRiskMembersDescriptionWithAppNone"
) | i18n: dataService.appAtRiskMembers.applicationName ) | i18n: drawerDetails.appAtRiskMembers.applicationName
}} }}
</div> </div>
<div class="tw-mt-1"> <div class="tw-mt-1">
<ng-container *ngFor="let member of dataService.appAtRiskMembers.members"> <ng-container *ngFor="let member of drawerDetails.appAtRiskMembers.members">
<div>{{ member.email }}</div> <div>{{ member.email }}</div>
</ng-container> </ng-container>
</div> </div>
</bit-drawer-body> </bit-drawer-body>
</ng-container> }
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.OrgAtRiskApps)"> @if (dataService.isActiveDrawerType(drawerTypes.OrgAtRiskApps)) {
<bit-drawer-header <bit-drawer-header
title="{{ 'atRiskApplicationsWithCount' | i18n: dataService.atRiskAppDetails.length }}" title="{{ 'atRiskApplicationsWithCount' | i18n: drawerDetails.atRiskAppDetails.length }}"
> >
</bit-drawer-header> </bit-drawer-header>
<bit-drawer-body> <bit-drawer-body>
<span bitTypography="body2" class="tw-text-muted tw-text-sm">{{ <span bitTypography="body2" class="tw-text-muted tw-text-sm">{{
(dataService.atRiskAppDetails.length > 0 (drawerDetails.atRiskAppDetails.length > 0
? "atRiskApplicationsDescription" ? "atRiskApplicationsDescription"
: "atRiskApplicationsDescriptionNone" : "atRiskApplicationsDescriptionNone"
) | i18n ) | i18n
}}</span> }}</span>
<ng-container *ngIf="dataService.atRiskAppDetails.length > 0"> <ng-container *ngIf="drawerDetails.atRiskAppDetails.length > 0">
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted"> <div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
<div bitTypography="body2" class="tw-text-sm tw-font-bold"> <div bitTypography="body2" class="tw-text-sm tw-font-bold">
{{ "application" | i18n }} {{ "application" | i18n }}
@@ -132,7 +129,7 @@
{{ "atRiskPasswords" | i18n }} {{ "atRiskPasswords" | i18n }}
</div> </div>
</div> </div>
<ng-container *ngFor="let app of dataService.atRiskAppDetails"> <ng-container *ngFor="let app of drawerDetails.atRiskAppDetails">
<div class="tw-flex tw-justify-between tw-mt-2"> <div class="tw-flex tw-justify-between tw-mt-2">
<div>{{ app.applicationName }}</div> <div>{{ app.applicationName }}</div>
<div>{{ app.atRiskPasswordCount }}</div> <div>{{ app.atRiskPasswordCount }}</div>
@@ -140,6 +137,7 @@
</ng-container> </ng-container>
</ng-container> </ng-container>
</bit-drawer-body> </bit-drawer-body>
</ng-container> }
</bit-drawer> </bit-drawer>
}
</ng-container> </ng-container>

View File

@@ -61,6 +61,9 @@ export enum RiskInsightsTabType {
], ],
}) })
export class RiskInsightsComponent implements OnInit { export class RiskInsightsComponent implements OnInit {
private destroyRef = inject(DestroyRef);
private _isDrawerOpen: boolean = false;
tabIndex: RiskInsightsTabType = RiskInsightsTabType.AllApps; tabIndex: RiskInsightsTabType = RiskInsightsTabType.AllApps;
isRiskInsightsActivityTabFeatureEnabled: boolean = false; isRiskInsightsActivityTabFeatureEnabled: boolean = false;
@@ -73,7 +76,6 @@ export class RiskInsightsComponent implements OnInit {
notifiedMembersCount: number = 0; notifiedMembersCount: number = 0;
private organizationId: OrganizationId = "" as OrganizationId; private organizationId: OrganizationId = "" as OrganizationId;
private destroyRef = inject(DestroyRef);
isLoading$: Observable<boolean> = new Observable<boolean>(); isLoading$: Observable<boolean> = new Observable<boolean>();
isRefreshing$: Observable<boolean> = new Observable<boolean>(); isRefreshing$: Observable<boolean> = new Observable<boolean>();
@@ -136,6 +138,13 @@ export class RiskInsightsComponent implements OnInit {
); );
}, },
}); });
// Subscribe to drawer state changes
this.dataService.drawerDetails$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((details) => {
this._isDrawerOpen = details.open;
});
} }
/** /**
@@ -163,4 +172,27 @@ export class RiskInsightsComponent implements OnInit {
get drawerTypes(): typeof DrawerType { get drawerTypes(): typeof DrawerType {
return DrawerType; return DrawerType;
} }
/**
* Special case getter for syncing drawer state from service to component.
* This allows the template to use two-way binding while staying reactive.
*/
get isDrawerOpen() {
return this._isDrawerOpen;
}
/**
* Special case setter for syncing drawer state from component to service.
* When the drawer component closes the drawer, this syncs the state back to the service.
*/
set isDrawerOpen(value: boolean) {
if (this._isDrawerOpen !== value) {
this._isDrawerOpen = value;
// Close the drawer in the service if the drawer component closed the drawer
if (!value) {
this.dataService.closeDrawer();
}
}
}
} }