diff --git a/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts b/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts index 69dd360ad31..d098be56663 100644 --- a/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Directive, OnDestroy } from "@angular/core"; import { BehaviorSubject, @@ -36,7 +34,7 @@ import { import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @Directive() -export class CipherReportComponent implements OnDestroy { +export abstract class CipherReportComponent implements OnDestroy { isAdminConsoleActive = false; loading = false; @@ -44,16 +42,16 @@ export class CipherReportComponent implements OnDestroy { ciphers: CipherView[] = []; allCiphers: CipherView[] = []; dataSource = new TableDataSource(); - organization: Organization; - organizations: Organization[]; + organization: Organization | undefined = undefined; + organizations: Organization[] = []; organizations$: Observable; filterStatus: any = [0]; showFilterToggle: boolean = false; vaultMsg: string = "vault"; - currentFilterStatus: number | string; + currentFilterStatus: number | string = 0; protected filterOrgStatus$ = new BehaviorSubject(0); - private destroyed$: Subject = new Subject(); + protected destroyed$: Subject = new Subject(); private vaultItemDialogRef?: DialogRef | undefined; constructor( @@ -107,7 +105,7 @@ export class CipherReportComponent implements OnDestroy { if (filterId === 0) { cipherCount = this.allCiphers.length; } else if (filterId === 1) { - cipherCount = this.allCiphers.filter((c) => c.organizationId === null).length; + cipherCount = this.allCiphers.filter((c) => !c.organizationId).length; } else { this.organizations.filter((org: Organization) => { if (org.id === filterId) { @@ -121,9 +119,9 @@ export class CipherReportComponent implements OnDestroy { } async filterOrgToggle(status: any) { - let filter = null; + let filter = (c: CipherView) => true; if (typeof status === "number" && status === 1) { - filter = (c: CipherView) => c.organizationId == null; + filter = (c: CipherView) => !c.organizationId; } else if (typeof status === "string") { const orgId = status as OrganizationId; filter = (c: CipherView) => c.organizationId === orgId; @@ -185,7 +183,7 @@ export class CipherReportComponent implements OnDestroy { cipher: CipherView, activeCollectionId?: CollectionId, ) { - const disableForm = cipher ? !cipher.edit && !this.organization.canEditAllCiphers : false; + const disableForm = cipher ? !cipher.edit && !this.organization?.canEditAllCiphers : false; this.vaultItemDialogRef = VaultItemDialogComponent.open(this.dialogService, { mode, @@ -230,10 +228,11 @@ export class CipherReportComponent implements OnDestroy { let updatedCipher = await this.cipherService.get(cipher.id, activeUserId); if (this.isAdminConsoleActive) { - updatedCipher = await this.adminConsoleCipherFormConfigService.getCipher( - cipher.id as CipherId, - this.organization, - ); + updatedCipher = + (await this.adminConsoleCipherFormConfigService.getCipher( + cipher.id as CipherId, + this.organization!, + )) ?? updatedCipher; } // convert cipher to cipher view model diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.spec.ts index 052e3bc7cfe..560245bdc34 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.spec.ts @@ -90,6 +90,7 @@ describe("ExposedPasswordsReportComponent", () => { }); beforeEach(() => { + jest.clearAllMocks(); fixture = TestBed.createComponent(ExposedPasswordsReportComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts index 80893737ffd..64a851e120e 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts @@ -1,3 +1,4 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { MockProxy, mock } from "jest-mock-extended"; import { of } from "rxjs"; @@ -29,14 +30,13 @@ describe("InactiveTwoFactorReportComponent", () => { const userId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(userId); - beforeEach(() => { + beforeEach(async () => { let cipherFormConfigServiceMock: MockProxy; organizationService = mock(); organizationService.organizations$.mockReturnValue(of([])); syncServiceMock = mock(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - TestBed.configureTestingModule({ + + await TestBed.configureTestingModule({ declarations: [InactiveTwoFactorReportComponent, I18nPipe], providers: [ { @@ -80,9 +80,7 @@ describe("InactiveTwoFactorReportComponent", () => { useValue: adminConsoleCipherFormConfigServiceMock, }, ], - schemas: [], - // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports - errorOnUnknownElements: false, + schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); }); diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts index 2a8ec12ac6a..9d7de688f3e 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts @@ -1,6 +1,4 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from "@angular/core"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -19,9 +17,8 @@ import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/se import { CipherReportComponent } from "./cipher-report.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, selector: "app-inactive-two-factor-report", templateUrl: "inactive-two-factor-report.component.html", standalone: false, @@ -42,6 +39,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl syncService: SyncService, cipherFormConfigService: CipherFormConfigService, adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + protected changeDetectorRef: ChangeDetectorRef, ) { super( cipherService, @@ -86,6 +84,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl this.filterCiphersByOrg(inactive2faCiphers); this.cipherDocs = docs; + this.changeDetectorRef.markForCheck(); } } @@ -157,6 +156,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl } this.services.set(serviceData.domain, serviceData.documentation); } + this.changeDetectorRef.markForCheck(); } /** diff --git a/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts index fde9c35a6de..17555e617cb 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts @@ -1,16 +1,12 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; +import { ChangeDetectorRef, Component, OnInit, ChangeDetectionStrategy } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom, map, takeUntil } from "rxjs"; -import { - getOrganizationById, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { getById } from "@bitwarden/common/platform/misc"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -23,9 +19,8 @@ import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vau import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../inactive-two-factor-report.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, selector: "app-inactive-two-factor-report", templateUrl: "../inactive-two-factor-report.component.html", providers: [ @@ -44,7 +39,7 @@ export class InactiveTwoFactorReportComponent implements OnInit { // Contains a list of ciphers, the user running the report, can manage - private manageableCiphers: Cipher[]; + private manageableCiphers: Cipher[] = []; constructor( cipherService: CipherService, @@ -58,6 +53,7 @@ export class InactiveTwoFactorReportComponent syncService: SyncService, cipherFormConfigService: CipherFormConfigService, adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + protected changeDetectorRef: ChangeDetectorRef, ) { super( cipherService, @@ -70,28 +66,37 @@ export class InactiveTwoFactorReportComponent syncService, cipherFormConfigService, adminConsoleCipherFormConfigService, + changeDetectorRef, ); } async ngOnInit() { this.isAdminConsoleActive = true; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - this.organization = await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(params.organizationId)), - ); - this.manageableCiphers = await this.cipherService.getAll(userId); - await super.ngOnInit(); - }); + + this.route.parent?.parent?.params + ?.pipe(takeUntil(this.destroyed$)) + // eslint-disable-next-line rxjs/no-async-subscribe + .subscribe(async (params) => { + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + if (userId) { + this.organization = await firstValueFrom( + this.organizationService.organizations$(userId).pipe(getById(params.organizationId)), + ); + this.manageableCiphers = await this.cipherService.getAll(userId); + await super.ngOnInit(); + } + this.changeDetectorRef.markForCheck(); + }); } - getAllCiphers(): Promise { - return this.cipherService.getAllFromApiForOrganization(this.organization.id); + async getAllCiphers(): Promise { + if (this.organization) { + return await this.cipherService.getAllFromApiForOrganization(this.organization.id, true); + } + return []; } protected canManageCipher(c: CipherView): boolean {