diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts
index 38e1237318..51d35570cd 100644
--- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts
+++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts
@@ -35,6 +35,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { CipherId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
+import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import { LogService } from "@bitwarden/logging";
import {
@@ -191,6 +192,23 @@ export class RiskInsightsOrchestratorService {
this._generateReportTriggerSubject.next(true);
}
+ /**
+ * Gets the cipher icon for a given cipher ID
+ *
+ * @param cipherId The ID of the cipher to get the icon for
+ * @returns A CipherViewLike if found, otherwise undefined
+ */
+ getCipherIcon(cipherId: string): CipherViewLike | undefined {
+ const currentCiphers = this._ciphersSubject.value;
+ if (!currentCiphers) {
+ return undefined;
+ }
+
+ const foundCipher = currentCiphers.find((c) => c.id === cipherId);
+
+ return foundCipher;
+ }
+
/**
* Initializes the service context for a specific organization
*
diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts
index 7b9255ca82..d426a6b09c 100644
--- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts
+++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts
@@ -87,7 +87,9 @@ export class RiskInsightsDataService {
this._destroy$.complete();
}
- // ----- UI-triggered methods (delegate to orchestrator) -----
+ getCipherIcon(cipherId: string) {
+ return this.orchestrator.getCipherIcon(cipherId);
+ }
initializeForOrganization(organizationId: OrganizationId) {
this.orchestrator.initializeForOrganization(organizationId);
}
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html
index b1c2faa4f0..73ded40388 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html
@@ -22,10 +22,10 @@
bitTypography="h3"
class="!tw-mb-0"
aria-describedby="allAppsOrgAtRiskMembersLabel"
- >{{ applicationSummary.totalAtRiskMemberCount }}{{ applicationSummary().totalAtRiskMemberCount }}
{{
- "cardMetrics" | i18n: applicationSummary.totalMemberCount
+ "cardMetrics" | i18n: applicationSummary().totalMemberCount
}}
@@ -62,10 +62,10 @@
bitTypography="h3"
class="!tw-mb-0"
aria-describedby="allAppsOrgAtRiskApplicationsLabel"
- >{{ applicationSummary.totalAtRiskApplicationCount }}{{ applicationSummary().totalAtRiskApplicationCount }}
{{
- "cardMetrics" | i18n: applicationSummary.totalApplicationCount
+ "cardMetrics" | i18n: applicationSummary().totalApplicationCount
}}
@@ -95,11 +95,11 @@
type="button"
[buttonType]="'primary'"
bitButton
- [disabled]="!selectedUrls.size"
- [loading]="markingAsCritical"
+ [disabled]="!selectedUrls().size"
+ [loading]="markingAsCritical()"
(click)="markAppsAsCritical()"
>
-
+
{{ "markAppAsCritical" | i18n }}
@@ -108,7 +108,7 @@
[dataSource]="dataSource"
[showRowCheckBox]="true"
[showRowMenuForCriticalApps]="false"
- [selectedUrls]="selectedUrls"
+ [selectedUrls]="selectedUrls()"
[openApplication]="drawerDetails.invokerId || ''"
[checkboxChange]="onCheckboxChange"
[showAppAtRiskMembers]="showAppAtRiskMembers"
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts
index acad2901ba..3a9159ad68 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts
@@ -1,20 +1,23 @@
-import { Component, DestroyRef, inject, OnInit } from "@angular/core";
+import {
+ Component,
+ DestroyRef,
+ inject,
+ OnInit,
+ ChangeDetectionStrategy,
+ signal,
+} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormControl } from "@angular/forms";
-import { ActivatedRoute, Router } from "@angular/router";
+import { ActivatedRoute } from "@angular/router";
import { debounceTime } from "rxjs";
import { Security } from "@bitwarden/assets/svg";
-import {
- ApplicationHealthReportDetailEnriched,
- RiskInsightsDataService,
-} from "@bitwarden/bit-common/dirt/reports/risk-insights";
+import { RiskInsightsDataService } from "@bitwarden/bit-common/dirt/reports/risk-insights";
import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers";
import {
OrganizationReportSummary,
ReportStatus,
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import {
IconButtonModule,
@@ -29,12 +32,14 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
-import { AppTableRowScrollableComponent } from "../shared/app-table-row-scrollable.component";
+import {
+ ApplicationTableDataSource,
+ AppTableRowScrollableComponent,
+} from "../shared/app-table-row-scrollable.component";
import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.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: "dirt-all-applications",
templateUrl: "./all-applications.component.html",
imports: [
@@ -51,24 +56,25 @@ import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.co
],
})
export class AllApplicationsComponent implements OnInit {
- protected dataSource = new TableDataSource();
- protected selectedUrls: Set = new Set();
- protected searchControl = new FormControl("", { nonNullable: true });
- protected organization = new Organization();
- noItemsIcon = Security;
- protected markingAsCritical = false;
- protected applicationSummary: OrganizationReportSummary = createNewSummaryData();
- protected ReportStatusEnum = ReportStatus;
-
destroyRef = inject(DestroyRef);
+ protected ReportStatusEnum = ReportStatus;
+ protected noItemsIcon = Security;
+
+ // Standard properties
+ protected readonly dataSource = new TableDataSource();
+ protected readonly searchControl = new FormControl("", { nonNullable: true });
+
+ // Template driven properties
+ protected readonly selectedUrls = signal(new Set());
+ protected readonly markingAsCritical = signal(false);
+ protected readonly applicationSummary = signal(createNewSummaryData());
+
constructor(
protected i18nService: I18nService,
protected activatedRoute: ActivatedRoute,
protected toastService: ToastService,
protected dataService: RiskInsightsDataService,
- private router: Router,
- // protected allActivitiesService: AllActivitiesService,
) {
this.searchControl.valueChanges
.pipe(debounceTime(200), takeUntilDestroyed())
@@ -78,8 +84,21 @@ export class AllApplicationsComponent implements OnInit {
async ngOnInit() {
this.dataService.enrichedReportData$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
next: (report) => {
- this.applicationSummary = report?.summaryData ?? createNewSummaryData();
- this.dataSource.data = report?.reportData ?? [];
+ if (report != null) {
+ this.applicationSummary.set(report.summaryData);
+
+ // Map the report data to include the iconCipher for each application
+ const tableDataWithIcon = report.reportData.map((app) => ({
+ ...app,
+ iconCipher:
+ app.cipherIds.length > 0
+ ? this.dataService.getCipherIcon(app.cipherIds[0])
+ : undefined,
+ }));
+ this.dataSource.data = tableDataWithIcon;
+ } else {
+ this.dataSource.data = [];
+ }
},
error: () => {
this.dataSource.data = [];
@@ -88,15 +107,15 @@ export class AllApplicationsComponent implements OnInit {
}
isMarkedAsCriticalItem(applicationName: string) {
- return this.selectedUrls.has(applicationName);
+ return this.selectedUrls().has(applicationName);
}
markAppsAsCritical = async () => {
- this.markingAsCritical = true;
- const count = this.selectedUrls.size;
+ this.markingAsCritical.set(true);
+ const count = this.selectedUrls().size;
this.dataService
- .saveCriticalApplications(Array.from(this.selectedUrls))
+ .saveCriticalApplications(Array.from(this.selectedUrls()))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => {
@@ -105,8 +124,8 @@ export class AllApplicationsComponent implements OnInit {
title: "",
message: this.i18nService.t("criticalApplicationsMarkedSuccess", count.toString()),
});
- this.selectedUrls.clear();
- this.markingAsCritical = false;
+ this.selectedUrls.set(new Set());
+ this.markingAsCritical.set(false);
},
error: () => {
this.toastService.showToast({
@@ -125,9 +144,15 @@ export class AllApplicationsComponent implements OnInit {
onCheckboxChange = (applicationName: string, event: Event) => {
const isChecked = (event.target as HTMLInputElement).checked;
if (isChecked) {
- this.selectedUrls.add(applicationName);
+ this.selectedUrls.update((selectedUrls) => {
+ selectedUrls.add(applicationName);
+ return selectedUrls;
+ });
} else {
- this.selectedUrls.delete(applicationName);
+ this.selectedUrls.update((selectedUrls) => {
+ selectedUrls.delete(applicationName);
+ return selectedUrls;
+ });
}
};
}
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts
index 1ea745929d..b61190df66 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts
@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
-import { Component, DestroyRef, inject, OnInit } from "@angular/core";
+import { Component, DestroyRef, inject, OnInit, ChangeDetectionStrategy } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormControl } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
@@ -8,7 +8,6 @@ import { debounceTime, EMPTY, from, map, switchMap, take } from "rxjs";
import { Security } from "@bitwarden/assets/svg";
import {
- ApplicationHealthReportDetailEnriched,
CriticalAppsService,
RiskInsightsDataService,
RiskInsightsReportService,
@@ -30,12 +29,14 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
import { RiskInsightsTabType } from "../models/risk-insights.models";
-import { AppTableRowScrollableComponent } from "../shared/app-table-row-scrollable.component";
+import {
+ ApplicationTableDataSource,
+ AppTableRowScrollableComponent,
+} from "../shared/app-table-row-scrollable.component";
import { AccessIntelligenceSecurityTasksService } from "../shared/security-tasks.service";
-// 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: "dirt-critical-applications",
templateUrl: "./critical-applications.component.html",
imports: [
@@ -55,7 +56,7 @@ export class CriticalApplicationsComponent implements OnInit {
protected organizationId: OrganizationId;
noItemsIcon = Security;
- protected dataSource = new TableDataSource();
+ protected dataSource = new TableDataSource();
protected applicationSummary = {} as OrganizationReportSummary;
protected selectedIds: Set = new Set();
@@ -79,9 +80,24 @@ export class CriticalApplicationsComponent implements OnInit {
async ngOnInit() {
this.dataService.criticalReportResults$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
next: (criticalReport) => {
- this.dataSource.data = criticalReport?.reportData ?? [];
- this.applicationSummary = criticalReport?.summaryData ?? createNewSummaryData();
- this.enableRequestPasswordChange = criticalReport?.summaryData?.totalAtRiskMemberCount > 0;
+ if (criticalReport != null) {
+ // Map the report data to include the iconCipher for each application
+ const tableDataWithIcon = criticalReport.reportData.map((app) => ({
+ ...app,
+ iconCipher:
+ app.cipherIds.length > 0
+ ? this.dataService.getCipherIcon(app.cipherIds[0])
+ : undefined,
+ }));
+ this.dataSource.data = tableDataWithIcon;
+
+ this.applicationSummary = criticalReport.summaryData;
+ this.enableRequestPasswordChange = criticalReport.summaryData.totalAtRiskMemberCount > 0;
+ } else {
+ this.dataSource.data = [];
+ this.applicationSummary = createNewSummaryData();
+ this.enableRequestPasswordChange = false;
+ }
},
error: () => {
this.dataSource.data = [];
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html
index 79af3869d9..edd90eaf97 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html
@@ -46,10 +46,7 @@
[attr.aria-label]="'viewItem' | i18n"
>
- 0"
- [cipher]="row.cipherIds[0]"
- >
+
;
+ dataSource!: TableDataSource;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() showRowMenuForCriticalApps: boolean = false;
|