mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
[PM-20041] Marking Task as complete (#14980)
* When saving a cipher, mark any associated security tasks as complete * fix test error from encryption refactor * hide security tasks that are associated with deleted ciphers (#15247) * account for deleted ciphers for atRiskPasswordDescriptions
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, inject } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { map, switchMap } from "rxjs";
|
||||
import { combineLatest, map, switchMap } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks";
|
||||
import { AnchorLinkDirective, CalloutModule } from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
@@ -16,10 +17,26 @@ import { I18nPipe } from "@bitwarden/ui-common";
|
||||
})
|
||||
export class AtRiskPasswordCalloutComponent {
|
||||
private taskService = inject(TaskService);
|
||||
private cipherService = inject(CipherService);
|
||||
private activeAccount$ = inject(AccountService).activeAccount$.pipe(getUserId);
|
||||
|
||||
protected pendingTasks$ = this.activeAccount$.pipe(
|
||||
switchMap((userId) => this.taskService.pendingTasks$(userId)),
|
||||
map((tasks) => tasks.filter((t) => t.type === SecurityTaskType.UpdateAtRiskCredential)),
|
||||
switchMap((userId) =>
|
||||
combineLatest([
|
||||
this.taskService.pendingTasks$(userId),
|
||||
this.cipherService.cipherViews$(userId),
|
||||
]),
|
||||
),
|
||||
map(([tasks, ciphers]) =>
|
||||
tasks.filter((t) => {
|
||||
const associatedCipher = ciphers.find((c) => c.id === t.cipherId);
|
||||
|
||||
return (
|
||||
t.type === SecurityTaskType.UpdateAtRiskCredential &&
|
||||
associatedCipher &&
|
||||
!associatedCipher.isDeleted
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -203,6 +203,20 @@ describe("AtRiskPasswordsComponent", () => {
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].name).toBe("Item 1");
|
||||
});
|
||||
|
||||
it("should not show tasks associated with deleted ciphers", async () => {
|
||||
mockCiphers$.next([
|
||||
{
|
||||
id: "cipher",
|
||||
organizationId: "org",
|
||||
name: "Item 1",
|
||||
isDeleted: true,
|
||||
} as CipherView,
|
||||
]);
|
||||
|
||||
const items = await firstValueFrom(component["atRiskItems$"]);
|
||||
expect(items).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pageDescription$", () => {
|
||||
@@ -245,6 +259,19 @@ describe("AtRiskPasswordsComponent", () => {
|
||||
type: SecurityTaskType.UpdateAtRiskCredential,
|
||||
} as SecurityTask,
|
||||
]);
|
||||
mockCiphers$.next([
|
||||
{
|
||||
id: "cipher",
|
||||
organizationId: "org",
|
||||
name: "Item 1",
|
||||
} as CipherView,
|
||||
{
|
||||
id: "cipher2",
|
||||
organizationId: "org2",
|
||||
name: "Item 2",
|
||||
} as CipherView,
|
||||
]);
|
||||
|
||||
const description = await firstValueFrom(component["pageDescription$"]);
|
||||
expect(description).toBe("atRiskPasswordsDescMultiOrgPlural");
|
||||
});
|
||||
|
||||
@@ -155,32 +155,35 @@ export class AtRiskPasswordsComponent implements OnInit {
|
||||
(t) =>
|
||||
t.type === SecurityTaskType.UpdateAtRiskCredential &&
|
||||
t.cipherId != null &&
|
||||
ciphers[t.cipherId] != null,
|
||||
ciphers[t.cipherId] != null &&
|
||||
!ciphers[t.cipherId].isDeleted,
|
||||
)
|
||||
.map((t) => ciphers[t.cipherId!]),
|
||||
),
|
||||
);
|
||||
|
||||
protected pageDescription$ = this.activeUserData$.pipe(
|
||||
switchMap(({ tasks, userId }) => {
|
||||
const orgIds = new Set(tasks.map((t) => t.organizationId));
|
||||
protected pageDescription$ = combineLatest([this.activeUserData$, this.atRiskItems$]).pipe(
|
||||
switchMap(([{ userId }, atRiskCiphers]) => {
|
||||
const orgIds = new Set(
|
||||
atRiskCiphers.filter((c) => c.organizationId).map((c) => c.organizationId),
|
||||
) as Set<string>;
|
||||
if (orgIds.size === 1) {
|
||||
const [orgId] = orgIds;
|
||||
return this.organizationService.organizations$(userId).pipe(
|
||||
getOrganizationById(orgId),
|
||||
map((org) =>
|
||||
this.i18nService.t(
|
||||
tasks.length === 1
|
||||
atRiskCiphers.length === 1
|
||||
? "atRiskPasswordDescSingleOrg"
|
||||
: "atRiskPasswordsDescSingleOrgPlural",
|
||||
org?.name,
|
||||
tasks.length,
|
||||
atRiskCiphers.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return of(this.i18nService.t("atRiskPasswordsDescMultiOrgPlural", tasks.length));
|
||||
return of(this.i18nService.t("atRiskPasswordsDescMultiOrgPlural", atRiskCiphers.length));
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user