mirror of
https://github.com/bitwarden/browser
synced 2025-12-26 21:23:34 +00:00
* feat(dirt): add newApplications$ observable to orchestrator Add reactive observable that filters applicationData for unreviewed apps (reviewedDate === null). Observable automatically updates when report state changes through the pipeline. - Add newApplications$ observable with distinctUntilChanged - Filters rawReportData$.data.applicationData - Uses shareReplay for multi-subscriber efficiency Related to PM-27284 * feat(dirt): add saveApplicationReviewStatus$ to orchestrator Implement method to save application review status and critical flags. Updates all applications where reviewedDate === null to set current date, and marks selected applications as critical. - Add saveApplicationReviewStatus$() method - Add _updateReviewStatusAndCriticalFlags() helper - Uses existing encryption and API update patterns - Single API call for both review status and critical flags - Follows same pattern as saveCriticalApplications$() Related to PM-27284 * feat(dirt): expose newApplications$ in data service Expose orchestrator's newApplications$ observable and save method through RiskInsightsDataService facade. Maintains clean separation between orchestrator (business logic) and components (UI). - Expose newApplications$ observable - Expose saveApplicationReviewStatus() delegation method - Maintains facade pattern consistency Related to PM-27284 * feat(dirt): make AllActivitiesService reactive to new applications Update AllActivitiesService to subscribe to orchestrator's newApplications$ observable instead of receiving data through summary updates. - Subscribe to dataService.newApplications$ in constructor - Add setNewApplications() helper method - Remove newApplications update from setAllAppsReportSummary() - New applications now update reactively when review status changes Related to PM-27284 * feat(dirt): connect dialog to review status save method Update NewApplicationsDialogComponent to call the data service's saveApplicationReviewStatus method when marking applications as critical. - Inject RiskInsightsDataService - Replace placeholder onMarkAsCritical() with real implementation - Handle success/error cases with appropriate toast notifications - Close dialog on successful save - Show different messages based on whether apps were marked critical Related to PM-27284 * feat(dirt): add i18n strings for application review Add internationalization strings for the new applications review dialog success and error messages. - applicationReviewSaved: Success toast title - applicationsMarkedAsCritical: Success message when apps marked critical - newApplicationsReviewed: Success message when apps reviewed only - errorSavingReviewStatus: Error toast title - pleaseTryAgain: Error toast message Related to PM-27284 * fix(dirt): add subscription cleanup to AllActivitiesService Critical fix for production code quality and memory leak prevention. Adds takeUntil pattern to all subscriptions to comply with ADR-0003 (Observable Data Services) requirements. **Subscription Cleanup (ADR-0003 Compliance):** - Add takeUntil pattern to AllActivitiesService subscriptions - Add _destroy$ Subject and destroy() method - Prevents memory leaks by properly unsubscribing from observables - Follows Observable Data Services ADR requirements Changes: - Import Subject and takeUntil from rxjs - Add private _destroy$ Subject for cleanup coordination - Apply takeUntil(this._destroy$) to all 3 subscriptions: - enrichedReportData$ subscription - criticalReportResults$ subscription - newApplications$ subscription - Add destroy() method for proper resource cleanup This ensures proper resource cleanup and follows Bitwarden's architectural decision records for observable management. Related to PM-27284 * fix(dirt): replace manual takeUntil with takeUntilDestroyed in AllActivitiesService Fixes critical memory leak by replacing manual subscription cleanup with Angular's automatic DestroyRef-based cleanup pattern. **Changes:** - Replace `takeUntil(this._destroy$)` with `takeUntilDestroyed()` for all 3 subscriptions - Remove unused `_destroy$` Subject and manual `destroy()` method - Update imports to use `@angular/core/rxjs-interop` **Why:** - Manual `destroy()` method was never called anywhere in codebase - Subscriptions accumulated without cleanup, causing memory leaks - `takeUntilDestroyed()` uses Angular's DestroyRef for automatic cleanup - Aligns with ADR-0003 and .claude/CLAUDE.md requirements **Impact:** - Automatic subscription cleanup when service context is destroyed - Prevents memory leaks during hot module reloads and route changes - Reduces code complexity (no manual lifecycle management needed) Related to PM-27284 * refactor(dirt): remove newApplications from OrganizationReportSummary Removes redundant newApplications field from summary type and uses derived newApplications$ observable from orchestrator instead. **Changes:** - Remove newApplications from OrganizationReportSummary type definition - Remove dummy data array from RiskInsightsReportService.getApplicationsSummary() - Remove newApplications subscription from AllActivitiesService - Update AllActivityComponent to subscribe directly to dataService.newApplications$ **Why:** - Eliminates data redundancy (stored vs derived) - newApplications$ already computes from applicationData.reviewedDate === null - Single source of truth: applicationData is the source - Simplifies encrypted payload (less data in summary) - Better separation: stored data (counts) vs computed data (lists) **Impact:** - No functional changes - UI continues to display new applications correctly - Cleaner architecture with computed observable pattern * cleanup * fix(dirt): improve dialog type safety and error logging Addresses critical PR review issues in NewApplicationsDialogComponent: **Type Safety:** - Replace unsafe type casting `(this as any).dialogRef` with proper DialogRef injection - Inject DialogRef<boolean | undefined> using Angular's inject() function - Ensures type safety and prevents runtime errors from missing dialogRef **Error Handling:** - Add LogService to dialog component - Log errors with "[NewApplicationsDialog]" for debugging - Maintain user-facing error toast while adding server-side logging **Impact:** - Eliminates TypeScript safety bypasses - Improves production debugging capabilities - Follows Angular dependency injection best practices * fixing mock data and test cases for new apps * feat(dirt): create assign tasks view component Create standalone view component for task assignment UI that can be embedded within dialogs or other containers. - Add AssignTasksViewComponent with signal-based inputs/outputs - Use input.required<number>() for selectedApplicationsCount - Use output<void>() for tasksAssigned and back events - Implement task calculation using SecurityTasksApiService - Add onAssignTasks() method with loading state and error handling - Include task summary card UI matching password-change-metric style - Add proper subscription cleanup with takeUntilDestroyed (ADR-0003) - Buttons included in component template (not dialog footer) - Component retrieves organizationId from route params Related to PM-27619 * refactor(dirt): add multi-view state management to new applications dialog Add view state const object and properties to support toggling between application selection and embedded assign tasks component. - Add DialogView const object with SelectApplications and AssignTasks states (ADR-0025) - Add DialogView type for type safety - Add currentView property to track active view - Import AssignTasksViewComponent for embedded use - Add isCalculatingTasks loading state - Inject AllActivitiesService and SecurityTasksApiService for task checking - Implement OnInit with organizationId retrieval from route params - Add proper subscription cleanup with takeUntilDestroyed (ADR-0003) - Expose DialogView constants to template Related to PM-27619 * feat(dirt): integrate assign tasks view into dialog Implement logic to embed AssignTasksViewComponent within dialog and handle communication via event bindings. - Update onMarkAsCritical to check for tasks before closing dialog - Add checkForTasksToAssign() method using SecurityTasksApiService - Conditionally transition to AssignTasks view when tasks are available - Add onTasksAssigned() handler to close dialog after successful assignment - Add onBack() handler to navigate back to SelectApplications view - Add loading state guard to prevent double-click on Mark as Critical button - Only show success toast and close dialog if no tasks to assign Related to PM-27619 * feat(dirt): add embedded assign tasks view to dialog template Update dialog template to conditionally render embedded AssignTasksViewComponent using @if directive. - Add conditional rendering for SelectApplications and AssignTasks views - Update dialog title dynamically based on currentView - Embed dirt-assign-tasks-view component in AssignTasks view - Pass selectedApplicationsCount via input binding - Listen to tasksAssigned and back output events - Show footer buttons only for SelectApplications view - Add loading and disabled states to Mark as Critical button - Change Cancel button to not auto-close (user must navigate) Related to PM-27619 * feat(dirt): add i18n keys for assign tasks view Add localized strings for embedded assign tasks view component. * resolve organizationId and DI issues in assign tasks flow - Pass organizationId via dialog data to prevent async race conditions - Pass organizationId as input to AssignTasksViewComponent (embedded components can't access route params) - Add DefaultAdminTaskService to component providers to fix NullInjectorError - Remove unnecessary route subscription from embedded component - Follow password-change-metric.component.ts pattern for consistency - Add detailed comments explaining architectural decisions and bug fixes * cleanup styling * refactor(dirt): remove newApplications validation from OrganizationReportSummary type guard Removes redundant newApplications field validation from the OrganizationReportSummary type guard and related test cases. **Changes:** - Remove "newApplications" from allowed keys in isOrganizationReportSummary() - Remove newApplications array validation logic - Remove newApplications validation from validateOrganizationReportSummary() - Remove 2 test cases for newApplications validation - Remove newApplications field from 8 test data objects **Rationale:** The newApplications field was removed from OrganizationReportSummary type definition because it's derived data that can be calculated from applicationData (filtering where reviewedDate === null). The data is now accessed via the reactive newApplications$ observable instead of being stored redundantly in the summary object. **Impact:** - No functional changes - UI continues to display new applications via observable - Type guard now correctly validates the actual OrganizationReportSummary structure - Eliminates data redundancy and maintains single source of truth - All 43 tests passing * improve assign tasks view display - Remove illustration/preview section (mailbox icon and prompt text) - Show unique member count instead of calculated task count - Use reportSummary.totalCriticalAtRiskMemberCount from AllActivitiesService - Remove unused SecurityTasksApiService dependency - Follow same pattern as all-activity.component.ts for consistency * logic to fetch totals and new styling * Fix review applications review view and assign view flow * Fix null type checks * refactor assign tasks dialog: use callout component, add video, fix OnPush, improve error handling * Add columns, description, search, and bulk select to new applications dialog * Add count placeholder for critical applications marked message * Address claude comments --------- Co-authored-by: Tom <ttalty@bitwarden.com> Co-authored-by: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Co-authored-by: maxkpower <mpower@bitwarden.com>
167 lines
6.4 KiB
TypeScript
167 lines
6.4 KiB
TypeScript
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
import { ActivatedRoute } from "@angular/router";
|
|
import { firstValueFrom, lastValueFrom } from "rxjs";
|
|
|
|
import {
|
|
AllActivitiesService,
|
|
ApplicationHealthReportDetail,
|
|
ReportStatus,
|
|
RiskInsightsDataService,
|
|
} 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 { DialogService } from "@bitwarden/components";
|
|
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
|
|
|
import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.component";
|
|
|
|
import { ActivityCardComponent } from "./activity-card.component";
|
|
import { PasswordChangeMetricComponent } from "./activity-cards/password-change-metric.component";
|
|
import { NewApplicationsDialogComponent } from "./application-review-dialog/new-applications-dialog.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({
|
|
selector: "dirt-all-activity",
|
|
imports: [
|
|
ApplicationsLoadingComponent,
|
|
SharedModule,
|
|
ActivityCardComponent,
|
|
PasswordChangeMetricComponent,
|
|
],
|
|
templateUrl: "./all-activity.component.html",
|
|
})
|
|
export class AllActivityComponent implements OnInit {
|
|
organization: Organization | null = null;
|
|
totalCriticalAppsAtRiskMemberCount = 0;
|
|
totalCriticalAppsCount = 0;
|
|
totalCriticalAppsAtRiskCount = 0;
|
|
newApplicationsCount = 0;
|
|
newApplications: ApplicationHealthReportDetail[] = [];
|
|
passwordChangeMetricHasProgressBar = false;
|
|
allAppsHaveReviewDate = false;
|
|
isAllCaughtUp = false;
|
|
hasLoadedApplicationData = false;
|
|
|
|
destroyRef = inject(DestroyRef);
|
|
|
|
protected ReportStatusEnum = ReportStatus;
|
|
|
|
constructor(
|
|
private accountService: AccountService,
|
|
protected activatedRoute: ActivatedRoute,
|
|
protected allActivitiesService: AllActivitiesService,
|
|
protected dataService: RiskInsightsDataService,
|
|
private dialogService: DialogService,
|
|
protected organizationService: OrganizationService,
|
|
) {}
|
|
|
|
async ngOnInit(): Promise<void> {
|
|
const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId");
|
|
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
|
|
|
if (organizationId) {
|
|
this.organization =
|
|
(await firstValueFrom(
|
|
this.organizationService.organizations$(userId).pipe(getById(organizationId)),
|
|
)) ?? null;
|
|
|
|
this.allActivitiesService.reportSummary$
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe((summary) => {
|
|
this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount;
|
|
this.totalCriticalAppsCount = summary.totalCriticalApplicationCount;
|
|
this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount;
|
|
});
|
|
|
|
this.dataService.newApplications$
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe((newApps) => {
|
|
this.newApplications = newApps;
|
|
this.newApplicationsCount = newApps.length;
|
|
this.updateIsAllCaughtUp();
|
|
});
|
|
|
|
this.allActivitiesService.passwordChangeProgressMetricHasProgressBar$
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.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.
|
|
*/
|
|
async onReviewNewApplications() {
|
|
const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId");
|
|
|
|
if (!organizationId) {
|
|
return;
|
|
}
|
|
|
|
// Pass organizationId via dialog data instead of having the dialog retrieve it from route.
|
|
// This ensures organizationId is immediately available when dialog opens, preventing
|
|
// timing issues where the dialog's checkForTasksToAssign() method runs before
|
|
// organizationId is populated via async route subscription.
|
|
const dialogRef = NewApplicationsDialogComponent.open(this.dialogService, {
|
|
newApplications: this.newApplications,
|
|
organizationId: organizationId as OrganizationId,
|
|
});
|
|
|
|
await lastValueFrom(dialogRef.closed);
|
|
}
|
|
|
|
/**
|
|
* Handles the "View at-risk members" link click.
|
|
* Opens the at-risk members drawer for critical applications only.
|
|
*/
|
|
async onViewAtRiskMembers() {
|
|
await this.dataService.setDrawerForCriticalAtRiskMembers("activityTabAtRiskMembers");
|
|
}
|
|
|
|
/**
|
|
* Handles the "View at-risk applications" link click.
|
|
* Opens the at-risk applications drawer for critical applications only.
|
|
*/
|
|
async onViewAtRiskApplications() {
|
|
await this.dataService.setDrawerForCriticalAtRiskApps("activityTabAtRiskApplications");
|
|
}
|
|
}
|