From 0bfc5daa7c59cb39e965a2752a4e9e9b5f01d12a Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Mon, 29 Sep 2025 15:19:14 -0500 Subject: [PATCH] [PM-26074] All Activities tab - Updated UI (#16587) * PM-26074 simplified and update the all-activities tab * PM-26074 removed learn more * PM-26074 fixing missing line --- apps/web/src/locales/en/messages.json | 30 +++++++++++- .../services/all-activities.service.ts | 44 ++++++++++++++++++ .../reports/risk-insights/services/index.ts | 1 + .../access-intelligence.module.ts | 6 +++ .../activity-card.component.html | 17 +++++-- .../activity-card.component.ts | 30 +++++++++--- .../all-activity.component.html | 30 ++++++++---- .../all-activity.component.ts | 46 +++++++++---------- .../all-applications.component.ts | 3 ++ .../critical-applications.component.ts | 3 ++ 10 files changed, 168 insertions(+), 42 deletions(-) create mode 100644 bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b4ad8f2b2b..1b374c9747 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -59,9 +59,28 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { + "onceYouMarkCriticalApplicationsActivityDescription": { "message": "Once you mark applications critical, they will display here." }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", "placeholders": { @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts new file mode 100644 index 0000000000..f1eebf81d7 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts @@ -0,0 +1,44 @@ +import { BehaviorSubject } from "rxjs"; + +import { OrganizationReportSummary } from "../models/report-models"; + +export class AllActivitiesService { + /// This class is used to manage the summary of all applications + /// and critical applications. + /// Going forward, this class can be simplified by using the RiskInsightsDataService + /// as it contains the application summary data. + + private reportSummarySubject$ = new BehaviorSubject({ + totalMemberCount: 0, + totalCriticalMemberCount: 0, + totalAtRiskMemberCount: 0, + totalCriticalAtRiskMemberCount: 0, + totalApplicationCount: 0, + totalCriticalApplicationCount: 0, + totalAtRiskApplicationCount: 0, + totalCriticalAtRiskApplicationCount: 0, + newApplications: [], + }); + + reportSummary$ = this.reportSummarySubject$.asObservable(); + + setCriticalAppsReportSummary(summary: OrganizationReportSummary) { + this.reportSummarySubject$.next({ + ...this.reportSummarySubject$.getValue(), + totalCriticalApplicationCount: summary.totalApplicationCount, + totalCriticalAtRiskApplicationCount: summary.totalAtRiskApplicationCount, + totalCriticalMemberCount: summary.totalMemberCount, + totalCriticalAtRiskMemberCount: summary.totalAtRiskMemberCount, + }); + } + + setAllAppsReportSummary(summary: OrganizationReportSummary) { + this.reportSummarySubject$.next({ + ...this.reportSummarySubject$.getValue(), + totalMemberCount: summary.totalMemberCount, + totalAtRiskMemberCount: summary.totalAtRiskMemberCount, + totalApplicationCount: summary.totalApplicationCount, + totalAtRiskApplicationCount: summary.totalAtRiskApplicationCount, + }); + } +} diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/index.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/index.ts index e3f75ea0da..69d936d301 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/index.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/index.ts @@ -5,3 +5,4 @@ export * from "./critical-apps-api.service"; export * from "./risk-insights-api.service"; export * from "./risk-insights-report.service"; export * from "./risk-insights-data.service"; +export * from "./all-activities.service"; diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts index 1d80f2154b..6848220446 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts @@ -3,6 +3,7 @@ import { NgModule } from "@angular/core"; import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { CriticalAppsService } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { + AllActivitiesService, CriticalAppsApiService, MemberCipherDetailsApiService, PasswordHealthService, @@ -73,6 +74,11 @@ import { RiskInsightsComponent } from "./risk-insights.component"; useClass: CriticalAppsApiService, deps: [ApiService], }), + safeProvider({ + provide: AllActivitiesService, + useClass: AllActivitiesService, + deps: [], + }), ], }) export class AccessIntelligenceModule {} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html index 227ca55578..17ae964dbe 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html @@ -1,9 +1,18 @@
- {{ title | i18n }} + {{ title }}
- {{ cardMetrics | i18n: value }} + {{ cardMetrics }}
-
- {{ metricDescription | i18n }} +
+ {{ metricDescription }}
+ @if (showNavigationLink) { + + }
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts index 5b9433634e..7de339358f 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts @@ -1,13 +1,14 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; +import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { TypographyModule } from "@bitwarden/components"; +import { ButtonModule, LinkModule, TypographyModule } from "@bitwarden/components"; @Component({ selector: "dirt-activity-card", templateUrl: "./activity-card.component.html", - imports: [CommonModule, TypographyModule, JslibModule], + imports: [CommonModule, TypographyModule, JslibModule, LinkModule, ButtonModule], host: { class: "tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-border [&:not(bit-layout_*)]:tw-rounded-lg tw-rounded-lg tw-p-6", @@ -18,10 +19,6 @@ export class ActivityCardComponent { * The title of the card goes here */ @Input() title: string = ""; - /** - * The current value of the card as emphasized text - */ - @Input() value: number | null = null; /** * The card metrics text to display next to the value */ @@ -30,4 +27,25 @@ export class ActivityCardComponent { * The description text to display below the value and metrics */ @Input() metricDescription: string = ""; + + /** + * The link to navigate to for more information + */ + @Input() navigationLink: string = ""; + + /** + * The text to display for the navigation link + */ + @Input() navigationText: string = ""; + + /** + * Show Navigation link + */ + @Input() showNavigationLink: boolean = false; + + constructor(private router: Router) {} + + navigateToLink = async (navigationLink: string) => { + await this.router.navigateByUrl(navigationLink); + }; } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html index 35c9fb451e..6598d19717 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html @@ -21,19 +21,33 @@
+
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts index da78f385cb..e69dc2b06e 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts @@ -1,23 +1,22 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute } from "@angular/router"; -import { BehaviorSubject, combineLatest, firstValueFrom, of, switchMap } from "rxjs"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; import { - CriticalAppsService, + AllActivitiesService, RiskInsightsDataService, - RiskInsightsReportService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { getById } from "@bitwarden/common/platform/misc"; -import { OrganizationId } from "@bitwarden/common/types/guid"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { ActivityCardComponent } from "./activity-card.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; +import { RiskInsightsTabType } from "./risk-insights.component"; @Component({ selector: "tools-all-activity", @@ -28,8 +27,9 @@ export class AllActivityComponent implements OnInit { protected isLoading$ = this.dataService.isLoading$; protected noData$ = new BehaviorSubject(true); organization: Organization | null = null; - atRiskMemberCount = 0; - criticalApplicationsCount = 0; + totalCriticalAppsAtRiskMemberCount = 0; + totalCriticalAppsCount = 0; + totalCriticalAppsAtRiskCount = 0; destroyRef = inject(DestroyRef); @@ -43,21 +43,13 @@ export class AllActivityComponent implements OnInit { this.organizationService.organizations$(userId).pipe(getById(organizationId)), )) ?? null; - combineLatest([ - this.dataService.applications$, - this.criticalAppsService.getAppsListForOrg(organizationId as OrganizationId), - ]) - .pipe( - takeUntilDestroyed(this.destroyRef), - switchMap(([apps, criticalApps]) => { - const atRiskMembers = this.reportService.generateAtRiskMemberList(apps ?? []); - return of({ apps, atRiskMembers, criticalApps }); - }), - ) - .subscribe(({ apps, atRiskMembers, criticalApps }) => { - this.noData$.next((apps?.length ?? 0) === 0); - this.atRiskMemberCount = atRiskMembers?.length ?? 0; - this.criticalApplicationsCount = criticalApps?.length ?? 0; + this.allActivitiesService.reportSummary$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((summary) => { + this.noData$.next(summary.totalApplicationCount === 0); + this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount; + this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; + this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; }); } } @@ -67,7 +59,15 @@ export class AllActivityComponent implements OnInit { private accountService: AccountService, protected organizationService: OrganizationService, protected dataService: RiskInsightsDataService, - protected reportService: RiskInsightsReportService, - protected criticalAppsService: CriticalAppsService, + protected allActivitiesService: AllActivitiesService, ) {} + + get RiskInsightsTabType() { + return RiskInsightsTabType; + } + + getLinkForRiskInsightsTab(tabIndex: RiskInsightsTabType): string { + const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); + return `/organizations/${organizationId}/access-intelligence/risk-insights?tabIndex=${tabIndex}`; + } } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts index 390d1f8f9d..3b7490dbc1 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts @@ -6,6 +6,7 @@ import { combineLatest, debounceTime, firstValueFrom, map, Observable, of, switc import { Security } from "@bitwarden/assets/svg"; import { + AllActivitiesService, CriticalAppsService, RiskInsightsDataService, RiskInsightsReportService, @@ -120,6 +121,7 @@ export class AllApplicationsComponent implements OnInit { if (data) { this.dataSource.data = data; this.applicationSummary = this.reportService.generateApplicationsSummary(data); + this.allActivitiesService.setAllAppsReportSummary(this.applicationSummary); } if (organization) { this.organization = organization; @@ -142,6 +144,7 @@ export class AllApplicationsComponent implements OnInit { private accountService: AccountService, protected criticalAppsService: CriticalAppsService, protected riskInsightsEncryptionService: RiskInsightsEncryptionService, + protected allActivitiesService: AllActivitiesService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts index 9110779c98..f092b1575f 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts @@ -8,6 +8,7 @@ import { combineLatest, debounceTime, firstValueFrom, map, switchMap } from "rxj import { Security } from "@bitwarden/assets/svg"; import { + AllActivitiesService, CriticalAppsService, RiskInsightsDataService, RiskInsightsReportService, @@ -99,6 +100,7 @@ export class CriticalApplicationsComponent implements OnInit { this.dataSource.data = applications; this.applicationSummary = this.reportService.generateApplicationsSummary(applications); this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0; + this.allActivitiesService.setCriticalAppsReportSummary(this.applicationSummary); } }); } @@ -176,6 +178,7 @@ export class CriticalApplicationsComponent implements OnInit { private configService: ConfigService, private adminTaskService: DefaultAdminTaskService, private accountService: AccountService, + private allActivitiesService: AllActivitiesService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed())