1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-23 16:13:21 +00:00

PM-27739 per PR comments map ciphers to report data in the components

This commit is contained in:
voommen-livefront
2025-11-05 11:09:41 -06:00
parent b748e38f91
commit 0fde177546
7 changed files with 73 additions and 43 deletions

View File

@@ -92,8 +92,8 @@ export const mockApplicationData: OrganizationReportApplication[] = [
];
export const mockEnrichedReportData: ApplicationHealthReportDetailEnriched[] = [
{ ...mockApplication1, isMarkedAsCritical: true, ciphers: [] },
{ ...mockApplication2, isMarkedAsCritical: false, ciphers: [] },
{ ...mockApplication1, isMarkedAsCritical: true },
{ ...mockApplication2, isMarkedAsCritical: false },
];
export const mockCipherViews: CipherView[] = [

View File

@@ -1,5 +1,3 @@
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
ApplicationHealthReportDetail,
OrganizationReportApplication,
@@ -8,7 +6,6 @@ import {
export type ApplicationHealthReportDetailEnriched = ApplicationHealthReportDetail & {
isMarkedAsCritical: boolean;
ciphers: CipherView[];
};
export interface RiskInsightsEnrichedData {
reportData: ApplicationHealthReportDetailEnriched[];

View File

@@ -84,6 +84,9 @@ export class RiskInsightsOrchestratorService {
// ------------------------- Cipher data -------------------------
private _ciphersSubject = new BehaviorSubject<CipherView[] | null>(null);
private _ciphers$ = this._ciphersSubject.asObservable();
get ciphers$(): Observable<CipherView[]> {
return this._ciphers$;
}
private _hasCiphersSubject$ = new BehaviorSubject<boolean | null>(null);
hasCiphers$ = this._hasCiphersSubject$.asObservable();
@@ -235,7 +238,6 @@ export class RiskInsightsOrchestratorService {
application,
updatedApplicationData,
),
ciphers: [], // explicitly ignore ciphers at this stage
}),
);
@@ -368,7 +370,6 @@ export class RiskInsightsOrchestratorService {
application,
updatedApplicationData,
),
ciphers: [], // explicitly ignore ciphers at this stage
}),
);
@@ -504,7 +505,6 @@ export class RiskInsightsOrchestratorService {
application,
updatedApplicationData,
),
ciphers: [], // explicitly ignore ciphers at this stage
}),
);
// For now, merge the report with the critical marking flag to make the enriched type
@@ -662,7 +662,6 @@ export class RiskInsightsOrchestratorService {
application,
updatedApplicationData,
),
ciphers: [], // explicitly ignore ciphers at this stage
}),
);
@@ -956,8 +955,8 @@ export class RiskInsightsOrchestratorService {
*/
private _setupEnrichedReportData() {
// Setup the enriched report data pipeline
const enrichmentSubscription = combineLatest([this.rawReportData$, this._ciphers$]).pipe(
switchMap(([rawReportData, ciphers]) => {
const enrichmentSubscription = combineLatest([this.rawReportData$]).pipe(
switchMap(([rawReportData]) => {
this.logService.debug(
"[RiskInsightsOrchestratorService] Enriching report data with ciphers and critical app status",
);
@@ -968,7 +967,6 @@ export class RiskInsightsOrchestratorService {
const enrichedReports: ApplicationHealthReportDetailEnriched[] = rawReports.map((app) => ({
...app,
isMarkedAsCritical: this.reportService.isCriticalApplication(app, criticalAppsData),
ciphers: ciphers?.filter((cipher) => app.cipherIds.includes(cipher.id)) ?? [],
}));
const enrichedData = {

View File

@@ -2,6 +2,7 @@ import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs";
import { distinctUntilChanged, map } from "rxjs/operators";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { getAtRiskApplicationList, getAtRiskMemberList } from "../../helpers";
import {
@@ -38,6 +39,7 @@ export class RiskInsightsDataService {
readonly isGeneratingReport$: Observable<boolean> = of(false);
readonly criticalReportResults$: Observable<RiskInsightsEnrichedData | null> = of(null);
readonly hasCiphers$: Observable<boolean | null> = of(null);
readonly ciphers$: Observable<CipherView[]> = of([]);
// New applications that need review (reviewedDate === null)
readonly newApplications$: Observable<ApplicationHealthReportDetail[]> = of([]);
@@ -64,6 +66,7 @@ export class RiskInsightsDataService {
this.newApplications$ = this.orchestrator.newApplications$;
this.hasCiphers$ = this.orchestrator.hasCiphers$.pipe(distinctUntilChanged());
this.ciphers$ = this.orchestrator.ciphers$;
// Expose the loading state
this.reportStatus$ = this.reportState$.pipe(

View File

@@ -215,7 +215,6 @@ export class NewApplicationsDialogComponent {
isMarkedAsCritical: updatedStateApplicationData.some(
(a) => a.applicationName == application.applicationName && a.isCritical,
),
ciphers: [], // explicitly ignore ciphers at this stage
}),
) || [];
return from(

View File

@@ -1,8 +1,8 @@
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";
import { debounceTime } from "rxjs";
import { combineLatest, debounceTime, of, switchMap } from "rxjs";
import { Security } from "@bitwarden/assets/svg";
import {
@@ -16,6 +16,7 @@ import {
} 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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
IconButtonModule,
NoItemsModule,
@@ -31,9 +32,8 @@ import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pip
import { 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: [
@@ -49,7 +49,9 @@ import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.co
],
})
export class AllApplicationsComponent implements OnInit {
protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>();
protected dataSource = new TableDataSource<
ApplicationHealthReportDetailEnriched & { ciphers: CipherView[] }
>();
protected selectedUrls: Set<string> = new Set<string>();
protected searchControl = new FormControl("", { nonNullable: true });
protected organization = new Organization();
@@ -74,15 +76,31 @@ 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 ?? [];
},
error: () => {
this.dataSource.data = [];
},
});
combineLatest([this.dataService.enrichedReportData$, this.dataService.ciphers$])
.pipe(
switchMap(([report, ciphers]) => {
if (!report) {
return null;
}
// Map ciphers to each application
const reportWithCiphers = report.reportData.map((app) => ({
...app,
ciphers: ciphers.filter((cipher) => app.cipherIds.includes(cipher.id)),
}));
return of({ ...report, reportData: reportWithCiphers });
}),
takeUntilDestroyed(this.destroyRef),
)
.subscribe({
next: (report) => {
this.applicationSummary = report?.summaryData ?? createNewSummaryData();
this.dataSource.data = report?.reportData ?? [];
},
error: () => {
this.dataSource.data = [];
},
});
}
isMarkedAsCriticalItem(applicationName: string) {

View File

@@ -1,10 +1,10 @@
// 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";
import { debounceTime, EMPTY, map, switchMap } from "rxjs";
import { combineLatest, debounceTime, EMPTY, map, switchMap } from "rxjs";
import { Security } from "@bitwarden/assets/svg";
import {
@@ -28,9 +28,8 @@ import { RiskInsightsTabType } from "../models/risk-insights.models";
import { 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: [
@@ -72,18 +71,34 @@ 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;
},
error: () => {
this.dataSource.data = [];
this.applicationSummary = createNewSummaryData();
this.enableRequestPasswordChange = false;
},
});
combineLatest([this.dataService.criticalReportResults$, this.dataService.ciphers$])
.pipe(
map(([report, ciphers]) => {
if (!report) {
return null;
}
const reportWithCiphers = (report?.reportData ?? []).map((app) => ({
...app,
ciphers: ciphers.filter((cipher) => app.cipherIds.includes(cipher.id)),
}));
return { ...report, reportData: reportWithCiphers };
}),
takeUntilDestroyed(this.destroyRef),
)
.subscribe({
next: (criticalReport) => {
this.dataSource.data = criticalReport?.reportData ?? [];
this.applicationSummary = criticalReport?.summaryData ?? createNewSummaryData();
this.enableRequestPasswordChange =
criticalReport?.summaryData?.totalAtRiskMemberCount > 0;
},
error: () => {
this.dataSource.data = [];
this.applicationSummary = createNewSummaryData();
this.enableRequestPasswordChange = false;
},
});
this.activatedRoute.paramMap
.pipe(
takeUntilDestroyed(this.destroyRef),