diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.ts b/apps/web/src/app/admin-console/organizations/manage/events.component.ts index 78a6d6c0dac..62f6539cc16 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { concatMap, filter, firstValueFrom, lastValueFrom, map, switchMap, takeUntil } from "rxjs"; +import { concatMap, firstValueFrom, lastValueFrom, map, of, switchMap, takeUntil, tap } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; @@ -143,17 +143,23 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe getUserId, switchMap((userId) => this.providerService.get$(this.organization.providerId, userId)), map((provider) => provider != null && provider.canManageUsers), - filter((result) => result), - switchMap(() => this.apiService.getProviderUsers(this.organization.id)), - map((providerUsersResponse) => - providerUsersResponse.data.forEach((u) => { - const name = this.userNamePipe.transform(u); - this.orgUsersUserIdMap.set(u.userId, { - name: `${name} (${this.organization.providerName})`, - email: u.email, + switchMap((canManage) => { + if (canManage) { + return this.apiService.getProviderUsers(this.organization.providerId); + } + return of(null); + }), + tap((providerUsersResponse) => { + if (providerUsersResponse) { + providerUsersResponse.data.forEach((u) => { + const name = this.userNamePipe.transform(u); + this.orgUsersUserIdMap.set(u.userId, { + name: `${name} (${this.organization.providerName})`, + email: u.email, + }); }); - }), - ), + } + }), ), ); } catch (e) { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 474acfe3bfe..b18d14426bd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -355,6 +355,12 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-card.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-card.component.html index 756907d24e6..73f98034f0a 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-card.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-card.component.html @@ -2,7 +2,7 @@ {{ title }}
@if (iconClass) { - + } {{ cardMetrics }}
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-card.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-card.component.ts index 427e7262f50..24d931165a7 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-card.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-card.component.ts @@ -58,6 +58,14 @@ export class ActivityCardComponent { // eslint-disable-next-line @angular-eslint/prefer-signals @Input() iconClass: string | null = null; + /** + * CSS class for icon color (e.g., "tw-text-success", "tw-text-muted"). + * Defaults to "tw-text-muted" if not provided. + */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals + @Input() iconColorClass: string = "tw-text-muted"; + /** * Button text. If provided, a button will be displayed instead of a navigation link. */ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html index 82137c9acca..8cdb927ab65 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html @@ -45,10 +45,19 @@
  • diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts index 713fdfa414f..e8768c0c404 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts @@ -43,6 +43,9 @@ export class AllActivityComponent implements OnInit { newApplicationsCount = 0; newApplications: NewApplicationDetail[] = []; passwordChangeMetricHasProgressBar = false; + allAppsHaveReviewDate = false; + isAllCaughtUp = false; + hasLoadedApplicationData = false; destroyRef = inject(DestroyRef); @@ -80,6 +83,7 @@ export class AllActivityComponent implements OnInit { .subscribe((newApps) => { this.newApplications = newApps; this.newApplicationsCount = newApps.length; + this.updateIsAllCaughtUp(); }); this.allActivitiesService.passwordChangeProgressMetricHasProgressBar$ @@ -87,9 +91,39 @@ export class AllActivityComponent implements OnInit { .subscribe((hasProgressBar) => { this.passwordChangeMetricHasProgressBar = hasProgressBar; }); + + this.dataService.enrichedReportData$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enrichedData) => { + if (enrichedData?.applicationData && enrichedData.applicationData.length > 0) { + this.hasLoadedApplicationData = true; + // Check if all apps have a review date (not null and not undefined) + this.allAppsHaveReviewDate = enrichedData.applicationData.every( + (app) => app.reviewedDate !== null && app.reviewedDate !== undefined, + ); + } else { + this.hasLoadedApplicationData = enrichedData !== null; + this.allAppsHaveReviewDate = false; + } + this.updateIsAllCaughtUp(); + }); } } + /** + * Updates the isAllCaughtUp flag based on current state. + * Only shows "All caught up!" when: + * - Data has been loaded (hasLoadedApplicationData is true) + * - No new applications need review + * - All apps have a review date + */ + private updateIsAllCaughtUp(): void { + this.isAllCaughtUp = + this.hasLoadedApplicationData && + this.newApplicationsCount === 0 && + this.allAppsHaveReviewDate; + } + /** * Handles the review new applications button click. * Opens a dialog showing the list of new applications that can be marked as critical.