1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-27757] init new apps state (#17200)

* feat(dirt): add "needs review" state for applications needing initial review

- Add showNeedsReviewState to display warning when all apps lack review dates
- Track noAppsHaveReviewDate flag to identify unreviewed applications
- Add i18n strings for organization items count and review prompt
- Update activity card to show 3 states: all caught up, needs review, new apps
- Apply tw-col-span-2 to needs review card for better visibility

* refactor: split activity card states into separate @if blocks for readability

* fix: set hasLoadedApplicationData when summary data arrives

Previously, hasLoadedApplicationData was only set in the enrichedReportData$
subscription, which fired after reportSummary$ and newApplications$. This
caused a timing issue where showNeedsReviewState would remain false even when
newApplicationsCount === totalApplicationCount because the flag wasn't set yet.

Now we set hasLoadedApplicationData=true as soon as reportSummary$ arrives
with totalApplicationCount > 0, ensuring proper synchronization.

---------

Co-authored-by: Tom <ttalty@bitwarden.com>
This commit is contained in:
Alex
2025-11-11 13:11:33 -05:00
committed by GitHub
parent b05abdb99c
commit 089caf57c2
3 changed files with 86 additions and 21 deletions

View File

@@ -373,6 +373,21 @@
"noNewApplicationsToReviewAtThisTime": { "noNewApplicationsToReviewAtThisTime": {
"message": "No new applications to review at this time" "message": "No new applications to review at this time"
}, },
"organizationHasItemsSavedForApplications": {
"message": "Your organization has items saved for $COUNT$ applications",
"placeholders": {
"count": {
"content": "$1",
"example": "310"
}
}
},
"reviewApplicationsToSecureItems": {
"message": "Review applications to secure the items most critical to your organization's security"
},
"reviewApplications": {
"message": "Review applications"
},
"prioritizeCriticalApplications": { "prioritizeCriticalApplications": {
"message": "Prioritize critical applications" "message": "Prioritize critical applications"
}, },

View File

@@ -44,26 +44,53 @@
</dirt-activity-card> </dirt-activity-card>
</li> </li>
<li class="tw-col-span-1"> <!-- All Caught Up State -->
<dirt-activity-card @if (isAllCaughtUp) {
[title]="'newApplicationsCardTitle' | i18n" <li class="tw-col-span-1">
[cardMetrics]=" <dirt-activity-card
isAllCaughtUp [title]="'applicationsNeedingReview' | i18n"
? ('allCaughtUp' | i18n) [cardMetrics]="'allCaughtUp' | i18n"
: ('newApplicationsWithCount' | i18n: newApplicationsCount) [metricDescription]="'noNewApplicationsToReviewAtThisTime' | i18n"
" [iconClass]="'bwi-check-circle'"
[metricDescription]=" [iconColorClass]="'tw-text-success'"
isAllCaughtUp [buttonText]="''"
? ('noNewApplicationsToReviewAtThisTime' | i18n) [buttonType]="'primary'"
: ('newApplicationsDescription' | i18n) (buttonClick)="onReviewNewApplications()"
" >
[iconClass]="isAllCaughtUp ? 'bwi-check-circle' : 'bwi-exclamation-triangle'" </dirt-activity-card>
[iconColorClass]="isAllCaughtUp ? 'tw-text-success' : 'tw-text-warning'" </li>
[buttonText]="isAllCaughtUp ? '' : ('reviewNow' | i18n)" }
[buttonType]="'primary'" <!-- Needs Review State (No apps have been reviewed) -->
(buttonClick)="onReviewNewApplications()" @else if (showNeedsReviewState) {
> <li class="tw-col-span-2">
</dirt-activity-card> <dirt-activity-card
</li> [title]="'applicationsNeedingReview' | i18n"
[cardMetrics]="'organizationHasItemsSavedForApplications' | i18n: totalApplicationCount"
[metricDescription]="'reviewApplicationsToSecureItems' | i18n"
[iconClass]="'bwi-exclamation-triangle'"
[iconColorClass]="'tw-text-warning'"
[buttonText]="'reviewApplications' | i18n"
[buttonType]="'primary'"
(buttonClick)="onReviewNewApplications()"
>
</dirt-activity-card>
</li>
}
<!-- Default State (New applications to review) -->
@else {
<li class="tw-col-span-1">
<dirt-activity-card
[title]="'applicationsNeedingReview' | i18n"
[cardMetrics]="'newApplicationsWithCount' | i18n: newApplicationsCount"
[metricDescription]="'newApplicationsDescription' | i18n"
[iconClass]="'bwi-exclamation-triangle'"
[iconColorClass]="'tw-text-muted'"
[buttonText]="'reviewNow' | i18n"
[buttonType]="'primary'"
(buttonClick)="onReviewNewApplications()"
>
</dirt-activity-card>
</li>
}
</ul> </ul>
} }

View File

@@ -39,12 +39,14 @@ export class AllActivityComponent implements OnInit {
totalCriticalAppsAtRiskMemberCount = 0; totalCriticalAppsAtRiskMemberCount = 0;
totalCriticalAppsCount = 0; totalCriticalAppsCount = 0;
totalCriticalAppsAtRiskCount = 0; totalCriticalAppsAtRiskCount = 0;
totalApplicationCount = 0;
newApplicationsCount = 0; newApplicationsCount = 0;
newApplications: ApplicationHealthReportDetail[] = []; newApplications: ApplicationHealthReportDetail[] = [];
extendPasswordChangeWidget = false; extendPasswordChangeWidget = false;
allAppsHaveReviewDate = false; allAppsHaveReviewDate = false;
isAllCaughtUp = false; isAllCaughtUp = false;
hasLoadedApplicationData = false; hasLoadedApplicationData = false;
showNeedsReviewState = false;
destroyRef = inject(DestroyRef); destroyRef = inject(DestroyRef);
@@ -65,6 +67,12 @@ export class AllActivityComponent implements OnInit {
this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount; this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount;
this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; this.totalCriticalAppsCount = summary.totalCriticalApplicationCount;
this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount;
this.totalApplicationCount = summary.totalApplicationCount;
// If we have application data, mark as loaded
if (summary.totalApplicationCount > 0) {
this.hasLoadedApplicationData = true;
}
this.updateShowNeedsReviewState();
}); });
this.dataService.newApplications$ this.dataService.newApplications$
@@ -73,6 +81,7 @@ export class AllActivityComponent implements OnInit {
this.newApplications = newApps; this.newApplications = newApps;
this.newApplicationsCount = newApps.length; this.newApplicationsCount = newApps.length;
this.updateIsAllCaughtUp(); this.updateIsAllCaughtUp();
this.updateShowNeedsReviewState();
}); });
this.allActivitiesService.extendPasswordChangeWidget$ this.allActivitiesService.extendPasswordChangeWidget$
@@ -112,6 +121,20 @@ export class AllActivityComponent implements OnInit {
this.allAppsHaveReviewDate; this.allAppsHaveReviewDate;
} }
/**
* Updates the showNeedsReviewState flag based on current state.
* This state is shown when:
* - Data has been loaded
* - There are applications (totalApplicationCount > 0)
* - ALL apps do NOT have a review date (newApplicationsCount === totalApplicationCount)
*/
private updateShowNeedsReviewState(): void {
this.showNeedsReviewState =
this.hasLoadedApplicationData &&
this.totalApplicationCount > 0 &&
this.newApplicationsCount === this.totalApplicationCount;
}
/** /**
* Handles the review new applications button click. * Handles the review new applications button click.
* Opens a dialog showing the list of new applications that can be marked as critical. * Opens a dialog showing the list of new applications that can be marked as critical.