mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-27739] Fix bug for icons not showing on application tables (#17373)
* Added function to get a cipher icon for application tables. Update all application component to use signal properties * Fix type * Handle no ciphers on application
This commit is contained in:
@@ -35,6 +35,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { CipherId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { LogService } from "@bitwarden/logging";
|
||||
|
||||
import {
|
||||
@@ -191,6 +192,23 @@ export class RiskInsightsOrchestratorService {
|
||||
this._generateReportTriggerSubject.next(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cipher icon for a given cipher ID
|
||||
*
|
||||
* @param cipherId The ID of the cipher to get the icon for
|
||||
* @returns A CipherViewLike if found, otherwise undefined
|
||||
*/
|
||||
getCipherIcon(cipherId: string): CipherViewLike | undefined {
|
||||
const currentCiphers = this._ciphersSubject.value;
|
||||
if (!currentCiphers) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const foundCipher = currentCiphers.find((c) => c.id === cipherId);
|
||||
|
||||
return foundCipher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the service context for a specific organization
|
||||
*
|
||||
|
||||
@@ -87,7 +87,9 @@ export class RiskInsightsDataService {
|
||||
this._destroy$.complete();
|
||||
}
|
||||
|
||||
// ----- UI-triggered methods (delegate to orchestrator) -----
|
||||
getCipherIcon(cipherId: string) {
|
||||
return this.orchestrator.getCipherIcon(cipherId);
|
||||
}
|
||||
initializeForOrganization(organizationId: OrganizationId) {
|
||||
this.orchestrator.initializeForOrganization(organizationId);
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
bitTypography="h3"
|
||||
class="!tw-mb-0"
|
||||
aria-describedby="allAppsOrgAtRiskMembersLabel"
|
||||
>{{ applicationSummary.totalAtRiskMemberCount }}</span
|
||||
>{{ applicationSummary().totalAtRiskMemberCount }}</span
|
||||
>
|
||||
<span bitTypography="body2">{{
|
||||
"cardMetrics" | i18n: applicationSummary.totalMemberCount
|
||||
"cardMetrics" | i18n: applicationSummary().totalMemberCount
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-items-baseline tw-mt-1 tw-gap-2">
|
||||
@@ -62,10 +62,10 @@
|
||||
bitTypography="h3"
|
||||
class="!tw-mb-0"
|
||||
aria-describedby="allAppsOrgAtRiskApplicationsLabel"
|
||||
>{{ applicationSummary.totalAtRiskApplicationCount }}</span
|
||||
>{{ applicationSummary().totalAtRiskApplicationCount }}</span
|
||||
>
|
||||
<span bitTypography="body2">{{
|
||||
"cardMetrics" | i18n: applicationSummary.totalApplicationCount
|
||||
"cardMetrics" | i18n: applicationSummary().totalApplicationCount
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-items-baseline tw-mt-1 tw-gap-2">
|
||||
@@ -95,11 +95,11 @@
|
||||
type="button"
|
||||
[buttonType]="'primary'"
|
||||
bitButton
|
||||
[disabled]="!selectedUrls.size"
|
||||
[loading]="markingAsCritical"
|
||||
[disabled]="!selectedUrls().size"
|
||||
[loading]="markingAsCritical()"
|
||||
(click)="markAppsAsCritical()"
|
||||
>
|
||||
<i class="bwi tw-mr-2" [ngClass]="selectedUrls.size ? 'bwi-star-f' : 'bwi-star'"></i>
|
||||
<i class="bwi tw-mr-2" [ngClass]="selectedUrls().size ? 'bwi-star-f' : 'bwi-star'"></i>
|
||||
{{ "markAppAsCritical" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@
|
||||
[dataSource]="dataSource"
|
||||
[showRowCheckBox]="true"
|
||||
[showRowMenuForCriticalApps]="false"
|
||||
[selectedUrls]="selectedUrls"
|
||||
[selectedUrls]="selectedUrls()"
|
||||
[openApplication]="drawerDetails.invokerId || ''"
|
||||
[checkboxChange]="onCheckboxChange"
|
||||
[showAppAtRiskMembers]="showAppAtRiskMembers"
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
signal,
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { debounceTime } from "rxjs";
|
||||
|
||||
import { Security } from "@bitwarden/assets/svg";
|
||||
import {
|
||||
ApplicationHealthReportDetailEnriched,
|
||||
RiskInsightsDataService,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights";
|
||||
import { RiskInsightsDataService } from "@bitwarden/bit-common/dirt/reports/risk-insights";
|
||||
import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers";
|
||||
import {
|
||||
OrganizationReportSummary,
|
||||
ReportStatus,
|
||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import {
|
||||
IconButtonModule,
|
||||
@@ -29,12 +32,14 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
|
||||
|
||||
import { AppTableRowScrollableComponent } from "../shared/app-table-row-scrollable.component";
|
||||
import {
|
||||
ApplicationTableDataSource,
|
||||
AppTableRowScrollableComponent,
|
||||
} from "../shared/app-table-row-scrollable.component";
|
||||
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
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: "dirt-all-applications",
|
||||
templateUrl: "./all-applications.component.html",
|
||||
imports: [
|
||||
@@ -51,24 +56,25 @@ import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.co
|
||||
],
|
||||
})
|
||||
export class AllApplicationsComponent implements OnInit {
|
||||
protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>();
|
||||
protected selectedUrls: Set<string> = new Set<string>();
|
||||
protected searchControl = new FormControl("", { nonNullable: true });
|
||||
protected organization = new Organization();
|
||||
noItemsIcon = Security;
|
||||
protected markingAsCritical = false;
|
||||
protected applicationSummary: OrganizationReportSummary = createNewSummaryData();
|
||||
protected ReportStatusEnum = ReportStatus;
|
||||
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
protected ReportStatusEnum = ReportStatus;
|
||||
protected noItemsIcon = Security;
|
||||
|
||||
// Standard properties
|
||||
protected readonly dataSource = new TableDataSource<ApplicationTableDataSource>();
|
||||
protected readonly searchControl = new FormControl("", { nonNullable: true });
|
||||
|
||||
// Template driven properties
|
||||
protected readonly selectedUrls = signal(new Set<string>());
|
||||
protected readonly markingAsCritical = signal(false);
|
||||
protected readonly applicationSummary = signal<OrganizationReportSummary>(createNewSummaryData());
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected toastService: ToastService,
|
||||
protected dataService: RiskInsightsDataService,
|
||||
private router: Router,
|
||||
// protected allActivitiesService: AllActivitiesService,
|
||||
) {
|
||||
this.searchControl.valueChanges
|
||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||
@@ -78,8 +84,21 @@ export class AllApplicationsComponent implements OnInit {
|
||||
async ngOnInit() {
|
||||
this.dataService.enrichedReportData$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
||||
next: (report) => {
|
||||
this.applicationSummary = report?.summaryData ?? createNewSummaryData();
|
||||
this.dataSource.data = report?.reportData ?? [];
|
||||
if (report != null) {
|
||||
this.applicationSummary.set(report.summaryData);
|
||||
|
||||
// Map the report data to include the iconCipher for each application
|
||||
const tableDataWithIcon = report.reportData.map((app) => ({
|
||||
...app,
|
||||
iconCipher:
|
||||
app.cipherIds.length > 0
|
||||
? this.dataService.getCipherIcon(app.cipherIds[0])
|
||||
: undefined,
|
||||
}));
|
||||
this.dataSource.data = tableDataWithIcon;
|
||||
} else {
|
||||
this.dataSource.data = [];
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.dataSource.data = [];
|
||||
@@ -88,15 +107,15 @@ export class AllApplicationsComponent implements OnInit {
|
||||
}
|
||||
|
||||
isMarkedAsCriticalItem(applicationName: string) {
|
||||
return this.selectedUrls.has(applicationName);
|
||||
return this.selectedUrls().has(applicationName);
|
||||
}
|
||||
|
||||
markAppsAsCritical = async () => {
|
||||
this.markingAsCritical = true;
|
||||
const count = this.selectedUrls.size;
|
||||
this.markingAsCritical.set(true);
|
||||
const count = this.selectedUrls().size;
|
||||
|
||||
this.dataService
|
||||
.saveCriticalApplications(Array.from(this.selectedUrls))
|
||||
.saveCriticalApplications(Array.from(this.selectedUrls()))
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
@@ -105,8 +124,8 @@ export class AllApplicationsComponent implements OnInit {
|
||||
title: "",
|
||||
message: this.i18nService.t("criticalApplicationsMarkedSuccess", count.toString()),
|
||||
});
|
||||
this.selectedUrls.clear();
|
||||
this.markingAsCritical = false;
|
||||
this.selectedUrls.set(new Set<string>());
|
||||
this.markingAsCritical.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.toastService.showToast({
|
||||
@@ -125,9 +144,15 @@ export class AllApplicationsComponent implements OnInit {
|
||||
onCheckboxChange = (applicationName: string, event: Event) => {
|
||||
const isChecked = (event.target as HTMLInputElement).checked;
|
||||
if (isChecked) {
|
||||
this.selectedUrls.add(applicationName);
|
||||
this.selectedUrls.update((selectedUrls) => {
|
||||
selectedUrls.add(applicationName);
|
||||
return selectedUrls;
|
||||
});
|
||||
} else {
|
||||
this.selectedUrls.delete(applicationName);
|
||||
this.selectedUrls.update((selectedUrls) => {
|
||||
selectedUrls.delete(applicationName);
|
||||
return selectedUrls;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||
import { Component, DestroyRef, inject, OnInit, ChangeDetectionStrategy } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
@@ -8,7 +8,6 @@ import { debounceTime, EMPTY, from, map, switchMap, take } from "rxjs";
|
||||
|
||||
import { Security } from "@bitwarden/assets/svg";
|
||||
import {
|
||||
ApplicationHealthReportDetailEnriched,
|
||||
CriticalAppsService,
|
||||
RiskInsightsDataService,
|
||||
RiskInsightsReportService,
|
||||
@@ -30,12 +29,14 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
|
||||
|
||||
import { RiskInsightsTabType } from "../models/risk-insights.models";
|
||||
import { AppTableRowScrollableComponent } from "../shared/app-table-row-scrollable.component";
|
||||
import {
|
||||
ApplicationTableDataSource,
|
||||
AppTableRowScrollableComponent,
|
||||
} from "../shared/app-table-row-scrollable.component";
|
||||
import { AccessIntelligenceSecurityTasksService } from "../shared/security-tasks.service";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: "dirt-critical-applications",
|
||||
templateUrl: "./critical-applications.component.html",
|
||||
imports: [
|
||||
@@ -55,7 +56,7 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
protected organizationId: OrganizationId;
|
||||
noItemsIcon = Security;
|
||||
|
||||
protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>();
|
||||
protected dataSource = new TableDataSource<ApplicationTableDataSource>();
|
||||
protected applicationSummary = {} as OrganizationReportSummary;
|
||||
|
||||
protected selectedIds: Set<number> = new Set<number>();
|
||||
@@ -79,9 +80,24 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
async ngOnInit() {
|
||||
this.dataService.criticalReportResults$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
||||
next: (criticalReport) => {
|
||||
this.dataSource.data = criticalReport?.reportData ?? [];
|
||||
this.applicationSummary = criticalReport?.summaryData ?? createNewSummaryData();
|
||||
this.enableRequestPasswordChange = criticalReport?.summaryData?.totalAtRiskMemberCount > 0;
|
||||
if (criticalReport != null) {
|
||||
// Map the report data to include the iconCipher for each application
|
||||
const tableDataWithIcon = criticalReport.reportData.map((app) => ({
|
||||
...app,
|
||||
iconCipher:
|
||||
app.cipherIds.length > 0
|
||||
? this.dataService.getCipherIcon(app.cipherIds[0])
|
||||
: undefined,
|
||||
}));
|
||||
this.dataSource.data = tableDataWithIcon;
|
||||
|
||||
this.applicationSummary = criticalReport.summaryData;
|
||||
this.enableRequestPasswordChange = criticalReport.summaryData.totalAtRiskMemberCount > 0;
|
||||
} else {
|
||||
this.dataSource.data = [];
|
||||
this.applicationSummary = createNewSummaryData();
|
||||
this.enableRequestPasswordChange = false;
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.dataSource.data = [];
|
||||
|
||||
@@ -46,10 +46,7 @@
|
||||
[attr.aria-label]="'viewItem' | i18n"
|
||||
>
|
||||
<!-- Passing the first cipher of the application for app-vault-icon cipher input requirement -->
|
||||
<app-vault-icon
|
||||
*ngIf="row.cipherIds.length > 0"
|
||||
[cipher]="row.cipherIds[0]"
|
||||
></app-vault-icon>
|
||||
<app-vault-icon *ngIf="row.iconCipher" [cipher]="row.iconCipher"></app-vault-icon>
|
||||
</td>
|
||||
<td
|
||||
class="tw-cursor-pointer"
|
||||
|
||||
@@ -3,10 +3,15 @@ import { Component, Input } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApplicationHealthReportDetailEnriched } from "@bitwarden/bit-common/dirt/reports/risk-insights";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { MenuModule, TableDataSource, TableModule } from "@bitwarden/components";
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
|
||||
|
||||
export type ApplicationTableDataSource = ApplicationHealthReportDetailEnriched & {
|
||||
iconCipher: CipherViewLike | undefined;
|
||||
};
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
@@ -18,7 +23,7 @@ export class AppTableRowScrollableComponent {
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input()
|
||||
dataSource!: TableDataSource<ApplicationHealthReportDetailEnriched>;
|
||||
dataSource!: TableDataSource<ApplicationTableDataSource>;
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input() showRowMenuForCriticalApps: boolean = false;
|
||||
|
||||
Reference in New Issue
Block a user