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..cde5d5c8c66 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
@@ -2,16 +2,18 @@ import { CommonModule } from "@angular/common";
import { Component, DestroyRef, OnDestroy, OnInit, inject } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Router } from "@angular/router";
-import { EMPTY } from "rxjs";
+import { combineLatest, EMPTY } from "rxjs";
import { map, tap } from "rxjs/operators";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
DrawerType,
+ ReportStatus,
RiskInsightsDataService,
} from "@bitwarden/bit-common/dirt/reports/risk-insights";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import {
AsyncActionsModule,
@@ -26,7 +28,9 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod
import { AllActivityComponent } from "./activity/all-activity.component";
import { AllApplicationsComponent } from "./all-applications/all-applications.component";
import { CriticalApplicationsComponent } from "./critical-applications/critical-applications.component";
+import { EmptyStateCardComponent } from "./empty-state-card.component";
import { RiskInsightsTabType } from "./models/risk-insights.models";
+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
@@ -38,6 +42,7 @@ import { RiskInsightsTabType } from "./models/risk-insights.models";
ButtonModule,
CommonModule,
CriticalApplicationsComponent,
+ EmptyStateCardComponent,
JslibModule,
HeaderModule,
TabsModule,
@@ -45,30 +50,46 @@ import { RiskInsightsTabType } from "./models/risk-insights.models";
DrawerBodyComponent,
DrawerHeaderComponent,
AllActivityComponent,
+ ApplicationsLoadingComponent,
],
})
export class RiskInsightsComponent implements OnInit, OnDestroy {
private destroyRef = inject(DestroyRef);
private _isDrawerOpen: boolean = false;
+ protected ReportStatusEnum = ReportStatus;
tabIndex: RiskInsightsTabType = RiskInsightsTabType.AllApps;
isRiskInsightsActivityTabFeatureEnabled: boolean = false;
appsCount: number = 0;
- // Leaving this commented because it's not used but seems important
- // notifiedMembersCount: number = 0;
private organizationId: OrganizationId = "" as OrganizationId;
dataLastUpdated: Date | null = null;
+ // Empty state properties
+ protected organizationName = "";
+
+ // Empty state computed properties
+ protected emptyStateBenefits: [string, string][] = [
+ [this.i18nService.t("benefit1Title"), this.i18nService.t("benefit1Description")],
+ [this.i18nService.t("benefit2Title"), this.i18nService.t("benefit2Description")],
+ [this.i18nService.t("benefit3Title"), this.i18nService.t("benefit3Description")],
+ ];
+ protected emptyStateVideoSrc: string | null = "/videos/risk-insights-mark-as-critical.mp4";
+
+ protected IMPORT_ICON = "bwi bwi-download";
+
+ // TODO: See https://github.com/bitwarden/clients/pull/16832#discussion_r2474523235
+
constructor(
private route: ActivatedRoute,
private router: Router,
private configService: ConfigService,
protected dataService: RiskInsightsDataService,
+ protected i18nService: I18nService,
) {
- this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
+ this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(({ tabIndex }) => {
this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps;
});
@@ -89,7 +110,7 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {
tap((orgId) => {
if (orgId) {
// Initialize Data Service
- this.dataService.initializeForOrganization(orgId as OrganizationId);
+ void this.dataService.initializeForOrganization(orgId as OrganizationId);
this.organizationId = orgId as OrganizationId;
} else {
return EMPTY;
@@ -98,12 +119,17 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {
)
.subscribe();
- // Subscribe to report result details
- this.dataService.enrichedReportData$
+ // Combine report data, vault items check, organization details, and generation state
+ // This declarative pattern ensures proper cleanup and prevents memory leaks
+ combineLatest([this.dataService.enrichedReportData$, this.dataService.organizationDetails$])
.pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((report) => {
+ .subscribe(([report, orgDetails]) => {
+ // Update report state
this.appsCount = report?.reportData.length ?? 0;
this.dataLastUpdated = report?.creationDate ?? null;
+
+ // Update organization name
+ this.organizationName = orgDetails?.organizationName ?? "";
});
// Subscribe to drawer state changes
@@ -166,4 +192,19 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {
}
}
}
+
+ // Empty state methods
+
+ // TODO: import data button (we have this) OR button for adding new login items
+ // we want to add this new button as a second option on the empty state card
+
+ goToImportPage = () => {
+ void this.router.navigate([
+ "/organizations",
+ this.organizationId,
+ "settings",
+ "tools",
+ "import",
+ ]);
+ };
}
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 ea41dd0aff3..79af3869d99 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
@@ -45,7 +45,11 @@
tabindex="0"
[attr.aria-label]="'viewItem' | i18n"
>
- 0" [cipher]="row.ciphers[0]">
+
+ 0"
+ [cipher]="row.cipherIds[0]"
+ >
|
{{ "continue" | i18n }}
@@ -59,6 +61,8 @@
block
buttonType="secondary"
(click)="handleLoginWithPasskeyClick()"
+ [bitTooltip]="ssoRequired ? ('yourOrganizationRequiresSingleSignOn' | i18n) : ''"
+ [addTooltipToDescribedby]="ssoRequired"
[disabled]="ssoRequired"
>
@@ -67,7 +71,13 @@
- |