diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index 18df046b82c..e809ed60f2e 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -59,94 +59,4 @@ - - @if (dataService.drawerDetails$ | async; as drawerDetails) { - - - - - - {{ - (drawerDetails.atRiskMemberDetails.length > 0 - ? "atRiskMembersDescription" - : "atRiskMembersDescriptionNone" - ) | i18n - }} - -
-
- {{ "email" | i18n }} -
-
- {{ "atRiskPasswords" | i18n }} -
-
- -
-
{{ member.email }}
-
{{ member.atRiskPasswordCount }}
-
-
-
-
-
- - @if (dataService.isActiveDrawerType(drawerTypes.AppAtRiskMembers)) { - - - -
- {{ "atRiskMembersWithCount" | i18n: drawerDetails.appAtRiskMembers.members.length }} -
-
- {{ - (drawerDetails.appAtRiskMembers.members.length > 0 - ? "atRiskMembersDescriptionWithApp" - : "atRiskMembersDescriptionWithAppNone" - ) | i18n: drawerDetails.appAtRiskMembers.applicationName - }} -
-
- -
{{ member.email }}
-
-
-
- } - - @if (dataService.isActiveDrawerType(drawerTypes.OrgAtRiskApps)) { - - - - - {{ - (drawerDetails.atRiskAppDetails.length > 0 - ? "atRiskApplicationsDescription" - : "atRiskApplicationsDescriptionNone" - ) | i18n - }} - -
-
- {{ "application" | i18n }} -
-
- {{ "atRiskPasswords" | i18n }} -
-
- -
-
{{ app.applicationName }}
-
{{ app.atRiskPasswordCount }}
-
-
-
-
- } -
- } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index 8e58ba22454..598393a3af7 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -1,5 +1,12 @@ import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, OnDestroy, OnInit, inject } from "@angular/core"; +import { + Component, + DestroyRef, + OnDestroy, + OnInit, + inject, + ChangeDetectionStrategy, +} from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; import { EMPTY } from "rxjs"; @@ -13,24 +20,17 @@ import { import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { - AsyncActionsModule, - ButtonModule, - DrawerBodyComponent, - DrawerComponent, - DrawerHeaderComponent, - TabsModule, -} from "@bitwarden/components"; +import { AsyncActionsModule, ButtonModule, DialogService, TabsModule } from "@bitwarden/components"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { AllActivityComponent } from "./activity/all-activity.component"; import { AllApplicationsComponent } from "./all-applications/all-applications.component"; import { CriticalApplicationsComponent } from "./critical-applications/critical-applications.component"; import { RiskInsightsTabType } from "./models/risk-insights.models"; +import { RiskInsightsDrawerDialogComponent } from "./shared/risk-insights-drawer-dialog.component"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: "./risk-insights.component.html", imports: [ AllApplicationsComponent, @@ -41,9 +41,6 @@ import { RiskInsightsTabType } from "./models/risk-insights.models"; JslibModule, HeaderModule, TabsModule, - DrawerComponent, - DrawerBodyComponent, - DrawerHeaderComponent, AllActivityComponent, ], }) @@ -67,6 +64,7 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { private router: Router, private configService: ConfigService, protected dataService: RiskInsightsDataService, + protected dialogService: DialogService, ) { this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => { this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps; @@ -79,6 +77,16 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { this.isRiskInsightsActivityTabFeatureEnabled = isEnabled; this.tabIndex = 0; // default to first tab }); + + this.dataService.drawerDetails$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((details) => { + if (details.activeDrawerType !== DrawerType.None) { + this.dialogService.openDrawer(RiskInsightsDrawerDialogComponent, { + data: { ...details }, + }); + } + }); } async ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html new file mode 100644 index 00000000000..f4e01d66a27 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html @@ -0,0 +1,81 @@ +@if (isActiveDrawerType(drawerTypes.OrgAtRiskMembers)) { + + + {{ "atRiskMembersWithCount" | i18n: drawerDetails.atRiskMemberDetails.length }} + + + {{ + (drawerDetails.atRiskMemberDetails.length > 0 + ? "atRiskMembersDescription" + : "atRiskMembersDescriptionNone" + ) | i18n + }} + + +
+
+ {{ "email" | i18n }} +
+
+ {{ "atRiskPasswords" | i18n }} +
+
+ +
+
{{ member.email }}
+
{{ member.atRiskPasswordCount }}
+
+
+
+
+ +
+} + +@if (isActiveDrawerType(drawerTypes.AppAtRiskMembers)) { + + + {{ drawerDetails.appAtRiskMembers.applicationName }} + + +
+ {{ "atRiskMembersWithCount" | i18n: drawerDetails.appAtRiskMembers.members.length }} +
+
+ {{ + (drawerDetails.appAtRiskMembers.members.length > 0 + ? "atRiskMembersDescriptionWithApp" + : "atRiskMembersDescriptionWithAppNone" + ) | i18n: drawerDetails.appAtRiskMembers.applicationName + }} +
+
+ +
{{ member.email }}
+
+
+
+
+} + +@if (isActiveDrawerType(drawerTypes.OrgAtRiskApps)) { + + + {{ "atRiskApplicationsWithCount" | i18n: drawerDetails.atRiskAppDetails.length }} + + + {{ + (drawerDetails.atRiskAppDetails.length > 0 + ? "atRiskApplicationsDescription" + : "atRiskApplicationsDescriptionNone" + ) | i18n + }} + +
+
{{ app.applicationName }}
+
{{ app.atRiskPasswordCount }}
+
+
+
+
+} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.spec.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.spec.ts new file mode 100644 index 00000000000..2283f39d720 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.spec.ts @@ -0,0 +1,96 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { mock } from "jest-mock-extended"; + +import { DrawerDetails, DrawerType } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DIALOG_DATA } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { RiskInsightsDrawerDialogComponent } from "./risk-insights-drawer-dialog.component"; + +beforeAll(() => { + // Mock element.animate for jsdom + // the animate function is not available in jsdom, so we provide a mock implementation + // This is necessary for tests that rely on animations + // This mock does not perform any actual animations, it just provides a structure that allows tests + // to run without throwing errors related to missing animate function + if (!HTMLElement.prototype.animate) { + HTMLElement.prototype.animate = function () { + return { + play: () => {}, + pause: () => {}, + finish: () => {}, + cancel: () => {}, + reverse: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + onfinish: null, + oncancel: null, + startTime: 0, + currentTime: 0, + playbackRate: 1, + playState: "idle", + replaceState: "active", + effect: null, + finished: Promise.resolve(), + id: "", + remove: () => {}, + timeline: null, + ready: Promise.resolve(), + } as unknown as Animation; + }; + } +}); + +describe("DrawerDialogComponent", () => { + let component: RiskInsightsDrawerDialogComponent; + let fixture: ComponentFixture; + const mockI18nService = mock(); + const drawerDetails: DrawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.None, + atRiskMemberDetails: [], + appAtRiskMembers: null, + atRiskAppDetails: null, + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RiskInsightsDrawerDialogComponent, BrowserAnimationsModule], + providers: [ + { provide: DIALOG_DATA, useValue: drawerDetails }, + { provide: I18nPipe, useValue: mock() }, + { provide: I18nService, useValue: mockI18nService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(RiskInsightsDrawerDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + describe("drawerTypes getter", () => { + it("should return DrawerType enum", () => { + expect(component.drawerTypes).toBe(DrawerType); + }); + }); + + describe("isActiveDrawerType", () => { + it("should return true if type matches activeDrawerType", () => { + component.drawerDetails.activeDrawerType = DrawerType.None; + expect(component.isActiveDrawerType(DrawerType.None)).toBeTruthy(); + }); + + it("should return false if type does not match activeDrawerType", () => { + component.drawerDetails.activeDrawerType = DrawerType.None; + expect(component.isActiveDrawerType(DrawerType.AppAtRiskMembers)).toBeFalsy(); + }); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.ts new file mode 100644 index 00000000000..82cddda542c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.ts @@ -0,0 +1,23 @@ +import { Component, ChangeDetectionStrategy, Inject } from "@angular/core"; + +import { DrawerDetails, DrawerType } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { DIALOG_DATA } from "@bitwarden/components"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +@Component({ + imports: [SharedModule], + templateUrl: "./risk-insights-drawer-dialog.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RiskInsightsDrawerDialogComponent { + constructor(@Inject(DIALOG_DATA) public drawerDetails: DrawerDetails) {} + + // Get a list of drawer types + get drawerTypes(): typeof DrawerType { + return DrawerType; + } + + isActiveDrawerType(type: DrawerType): boolean { + return this.drawerDetails.activeDrawerType === type; + } +}