mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-17120] account deprovisioning banner (#13097)
* remove provider client privay banner, implement account deprovisioning banner * add copy, make state depend on org plan type and org id * cleanup * refactor, add test * cleanup * cleanup * add state migration * Fix lintter error
This commit is contained in:
@@ -121,6 +121,22 @@
|
|||||||
</app-side-nav>
|
</app-side-nav>
|
||||||
|
|
||||||
<ng-container *ngIf="organization$ | async as organization">
|
<ng-container *ngIf="organization$ | async as organization">
|
||||||
|
<bit-banner
|
||||||
|
*ngIf="showAccountDeprovisioningBanner$ | async"
|
||||||
|
(onClose)="bannerService.hideBanner(organization)"
|
||||||
|
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
|
||||||
|
>
|
||||||
|
{{ "accountDeprovisioningNotification" | i18n }}
|
||||||
|
<a
|
||||||
|
href="https://bitwarden.com/help/claimed-accounts"
|
||||||
|
bitLink
|
||||||
|
linkType="contrast"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{{ "learnMore" | i18n }}
|
||||||
|
</a>
|
||||||
|
</bit-banner>
|
||||||
<bit-banner
|
<bit-banner
|
||||||
*ngIf="organization.isProviderUser"
|
*ngIf="organization.isProviderUser"
|
||||||
[showClose]="false"
|
[showClose]="false"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||||
import { combineLatest, filter, firstValueFrom, map, Observable, switchMap } from "rxjs";
|
import { combineLatest, filter, map, Observable, switchMap, withLatestFrom } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import {
|
import {
|
||||||
@@ -22,6 +22,7 @@ import { PolicyType, ProviderStatusType } from "@bitwarden/common/admin-console/
|
|||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
|
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
@@ -32,6 +33,8 @@ import { OrgSwitcherComponent } from "../../../layouts/org-switcher/org-switcher
|
|||||||
import { WebLayoutModule } from "../../../layouts/web-layout.module";
|
import { WebLayoutModule } from "../../../layouts/web-layout.module";
|
||||||
import { AdminConsoleLogo } from "../../icons/admin-console-logo";
|
import { AdminConsoleLogo } from "../../icons/admin-console-logo";
|
||||||
|
|
||||||
|
import { AccountDeprovisioningBannerService } from "./services/account-deprovisioning-banner.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-organization-layout",
|
selector: "app-organization-layout",
|
||||||
templateUrl: "organization-layout.component.html",
|
templateUrl: "organization-layout.component.html",
|
||||||
@@ -61,6 +64,8 @@ export class OrganizationLayoutComponent implements OnInit {
|
|||||||
organizationIsUnmanaged$: Observable<boolean>;
|
organizationIsUnmanaged$: Observable<boolean>;
|
||||||
enterpriseOrganization$: Observable<boolean>;
|
enterpriseOrganization$: Observable<boolean>;
|
||||||
|
|
||||||
|
showAccountDeprovisioningBanner$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
@@ -68,19 +73,36 @@ export class OrganizationLayoutComponent implements OnInit {
|
|||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
|
protected bannerService: AccountDeprovisioningBannerService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
document.body.classList.remove("layout_frontend");
|
document.body.classList.remove("layout_frontend");
|
||||||
|
|
||||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
|
||||||
this.organization$ = this.route.params.pipe(
|
this.organization$ = this.route.params.pipe(
|
||||||
map((p) => p.organizationId),
|
map((p) => p.organizationId),
|
||||||
switchMap((id) => this.organizationService.organizations$(userId).pipe(getById(id))),
|
withLatestFrom(this.accountService.activeAccount$.pipe(getUserId)),
|
||||||
|
switchMap(([orgId, userId]) =>
|
||||||
|
this.organizationService.organizations$(userId).pipe(getById(orgId)),
|
||||||
|
),
|
||||||
filter((org) => org != null),
|
filter((org) => org != null),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.showAccountDeprovisioningBanner$ = combineLatest([
|
||||||
|
this.bannerService.showBanner$,
|
||||||
|
this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioningBanner),
|
||||||
|
this.organization$,
|
||||||
|
]).pipe(
|
||||||
|
map(
|
||||||
|
([dismissedOrgs, featureFlagEnabled, organization]) =>
|
||||||
|
organization.productTierType === ProductTierType.Enterprise &&
|
||||||
|
organization.isAdmin &&
|
||||||
|
!dismissedOrgs?.includes(organization.id) &&
|
||||||
|
featureFlagEnabled,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
this.canAccessExport$ = this.organization$.pipe(map((org) => org.canAccessExport));
|
this.canAccessExport$ = this.organization$.pipe(map((org) => org.canAccessExport));
|
||||||
|
|
||||||
this.showPaymentAndHistory$ = this.organization$.pipe(
|
this.showPaymentAndHistory$ = this.organization$.pipe(
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import {
|
||||||
|
FakeAccountService,
|
||||||
|
FakeStateProvider,
|
||||||
|
mockAccountServiceWith,
|
||||||
|
} from "@bitwarden/common/spec";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
|
import { AccountDeprovisioningBannerService } from "./account-deprovisioning-banner.service";
|
||||||
|
|
||||||
|
describe("Account Deprovisioning Banner Service", () => {
|
||||||
|
const userId = Utils.newGuid() as UserId;
|
||||||
|
let accountService: FakeAccountService;
|
||||||
|
let stateProvider: FakeStateProvider;
|
||||||
|
let bannerService: AccountDeprovisioningBannerService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
accountService = mockAccountServiceWith(userId);
|
||||||
|
stateProvider = new FakeStateProvider(accountService);
|
||||||
|
bannerService = new AccountDeprovisioningBannerService(stateProvider);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates state with single org", async () => {
|
||||||
|
const fakeOrg = new Organization();
|
||||||
|
fakeOrg.id = "123";
|
||||||
|
|
||||||
|
await bannerService.hideBanner(fakeOrg);
|
||||||
|
const state = await firstValueFrom(bannerService.showBanner$);
|
||||||
|
|
||||||
|
expect(state).toEqual([fakeOrg.id]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates state with multiple orgs", async () => {
|
||||||
|
const fakeOrg1 = new Organization();
|
||||||
|
fakeOrg1.id = "123";
|
||||||
|
const fakeOrg2 = new Organization();
|
||||||
|
fakeOrg2.id = "234";
|
||||||
|
const fakeOrg3 = new Organization();
|
||||||
|
fakeOrg3.id = "987";
|
||||||
|
|
||||||
|
await bannerService.hideBanner(fakeOrg1);
|
||||||
|
await bannerService.hideBanner(fakeOrg2);
|
||||||
|
await bannerService.hideBanner(fakeOrg3);
|
||||||
|
|
||||||
|
const state = await firstValueFrom(bannerService.showBanner$);
|
||||||
|
|
||||||
|
expect(state).toContain(fakeOrg1.id);
|
||||||
|
expect(state).toContain(fakeOrg2.id);
|
||||||
|
expect(state).toContain(fakeOrg3.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not add the same org id multiple times", async () => {
|
||||||
|
const fakeOrg = new Organization();
|
||||||
|
fakeOrg.id = "123";
|
||||||
|
|
||||||
|
await bannerService.hideBanner(fakeOrg);
|
||||||
|
await bannerService.hideBanner(fakeOrg);
|
||||||
|
|
||||||
|
const state = await firstValueFrom(bannerService.showBanner$);
|
||||||
|
|
||||||
|
expect(state).toEqual([fakeOrg.id]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not add null to the state", async () => {
|
||||||
|
await bannerService.hideBanner(null as unknown as Organization);
|
||||||
|
await bannerService.hideBanner(undefined as unknown as Organization);
|
||||||
|
|
||||||
|
const state = await firstValueFrom(bannerService.showBanner$);
|
||||||
|
|
||||||
|
expect(state).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import {
|
||||||
|
ACCOUNT_DEPROVISIONING_BANNER_DISK,
|
||||||
|
StateProvider,
|
||||||
|
UserKeyDefinition,
|
||||||
|
} from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
|
export const SHOW_BANNER_KEY = new UserKeyDefinition<string[]>(
|
||||||
|
ACCOUNT_DEPROVISIONING_BANNER_DISK,
|
||||||
|
"accountDeprovisioningBanner",
|
||||||
|
{
|
||||||
|
deserializer: (b) => b,
|
||||||
|
clearOn: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
@Injectable({ providedIn: "root" })
|
||||||
|
export class AccountDeprovisioningBannerService {
|
||||||
|
private _showBanner = this.stateProvider.getActive(SHOW_BANNER_KEY);
|
||||||
|
|
||||||
|
showBanner$ = this._showBanner.state$;
|
||||||
|
|
||||||
|
constructor(private stateProvider: StateProvider) {}
|
||||||
|
|
||||||
|
async hideBanner(organization: Organization) {
|
||||||
|
await this._showBanner.update((state) => {
|
||||||
|
if (!organization) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
if (!state) {
|
||||||
|
return [organization.id];
|
||||||
|
} else if (!state.includes(organization.id)) {
|
||||||
|
return [...state, organization.id];
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10346,6 +10346,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"accountDeprovisioningNotification" : {
|
||||||
|
"message": "Administrators now have the ability to delete member accounts that belong to a claimed domain."
|
||||||
|
},
|
||||||
"deleteManagedUserWarningDesc": {
|
"deleteManagedUserWarningDesc": {
|
||||||
"message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action."
|
"message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,25 +37,5 @@
|
|||||||
></bit-nav-item>
|
></bit-nav-item>
|
||||||
</app-side-nav>
|
</app-side-nav>
|
||||||
|
|
||||||
<bit-banner
|
|
||||||
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
|
|
||||||
(onClose)="(true)"
|
|
||||||
*ngIf="
|
|
||||||
(showProviderClientVaultPrivacyWarningBanner$ | async) &&
|
|
||||||
(providerClientVaultPrivacyBannerService.showBanner$ | async) != false
|
|
||||||
"
|
|
||||||
(onClose)="providerClientVaultPrivacyBannerService.hideBanner()"
|
|
||||||
>
|
|
||||||
{{ "providerClientVaultPrivacyNotification" | i18n }}
|
|
||||||
<a
|
|
||||||
href="https://bitwarden.com/contact/"
|
|
||||||
bitLink
|
|
||||||
linkType="secondary"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{{ "contactBitwardenSupport" | i18n }} </a
|
|
||||||
>.
|
|
||||||
</bit-banner>
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</app-layout>
|
</app-layout>
|
||||||
|
|||||||
@@ -10,27 +10,15 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { IconModule } from "@bitwarden/components";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { BannerModule, IconModule, LinkModule } from "@bitwarden/components";
|
|
||||||
import { ProviderPortalLogo } from "@bitwarden/web-vault/app/admin-console/icons/provider-portal-logo";
|
import { ProviderPortalLogo } from "@bitwarden/web-vault/app/admin-console/icons/provider-portal-logo";
|
||||||
import { WebLayoutModule } from "@bitwarden/web-vault/app/layouts/web-layout.module";
|
import { WebLayoutModule } from "@bitwarden/web-vault/app/layouts/web-layout.module";
|
||||||
|
|
||||||
import { ProviderClientVaultPrivacyBannerService } from "./services/provider-client-vault-privacy-banner.service";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "providers-layout",
|
selector: "providers-layout",
|
||||||
templateUrl: "providers-layout.component.html",
|
templateUrl: "providers-layout.component.html",
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [CommonModule, RouterModule, JslibModule, WebLayoutModule, IconModule],
|
||||||
CommonModule,
|
|
||||||
RouterModule,
|
|
||||||
JslibModule,
|
|
||||||
WebLayoutModule,
|
|
||||||
IconModule,
|
|
||||||
LinkModule,
|
|
||||||
BannerModule,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
||||||
protected readonly logo = ProviderPortalLogo;
|
protected readonly logo = ProviderPortalLogo;
|
||||||
@@ -41,15 +29,9 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
|||||||
protected isBillable: Observable<boolean>;
|
protected isBillable: Observable<boolean>;
|
||||||
protected canAccessBilling$: Observable<boolean>;
|
protected canAccessBilling$: Observable<boolean>;
|
||||||
|
|
||||||
protected showProviderClientVaultPrivacyWarningBanner$ = this.configService.getFeatureFlag$(
|
|
||||||
FeatureFlag.ProviderClientVaultPrivacyBanner,
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
private configService: ConfigService,
|
|
||||||
protected providerClientVaultPrivacyBannerService: ProviderClientVaultPrivacyBannerService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Injectable } from "@angular/core";
|
|
||||||
|
|
||||||
import {
|
|
||||||
StateProvider,
|
|
||||||
AC_BANNERS_DISMISSED_DISK,
|
|
||||||
UserKeyDefinition,
|
|
||||||
} from "@bitwarden/common/platform/state";
|
|
||||||
|
|
||||||
export const SHOW_BANNER_KEY = new UserKeyDefinition<boolean>(
|
|
||||||
AC_BANNERS_DISMISSED_DISK,
|
|
||||||
"showProviderClientVaultPrivacyBanner",
|
|
||||||
{
|
|
||||||
deserializer: (b) => b,
|
|
||||||
clearOn: [],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Displays a banner warning provider users that client organization vaults
|
|
||||||
* will soon become inaccessible directly. */
|
|
||||||
@Injectable({ providedIn: "root" })
|
|
||||||
export class ProviderClientVaultPrivacyBannerService {
|
|
||||||
private _showBanner = this.stateProvider.getActive(SHOW_BANNER_KEY);
|
|
||||||
|
|
||||||
showBanner$ = this._showBanner.state$;
|
|
||||||
|
|
||||||
constructor(private stateProvider: StateProvider) {}
|
|
||||||
|
|
||||||
async hideBanner() {
|
|
||||||
await this._showBanner.update(() => false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -49,6 +49,7 @@ export enum FeatureFlag {
|
|||||||
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
|
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
|
||||||
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
|
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
|
||||||
ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs",
|
ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs",
|
||||||
|
AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
|
||||||
NewDeviceVerification = "new-device-verification",
|
NewDeviceVerification = "new-device-verification",
|
||||||
PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
|
PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
|
||||||
}
|
}
|
||||||
@@ -110,6 +111,7 @@ export const DefaultFeatureFlagValue = {
|
|||||||
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
|
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
|
||||||
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
|
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
|
||||||
[FeatureFlag.ResellerManagedOrgAlert]: FALSE,
|
[FeatureFlag.ResellerManagedOrgAlert]: FALSE,
|
||||||
|
[FeatureFlag.AccountDeprovisioningBanner]: FALSE,
|
||||||
[FeatureFlag.NewDeviceVerification]: FALSE,
|
[FeatureFlag.NewDeviceVerification]: FALSE,
|
||||||
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
|
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
|
||||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||||
|
|||||||
@@ -29,9 +29,13 @@ export const ORGANIZATION_MANAGEMENT_PREFERENCES_DISK = new StateDefinition(
|
|||||||
web: "disk-local",
|
web: "disk-local",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
export const AC_BANNERS_DISMISSED_DISK = new StateDefinition("acBannersDismissed", "disk", {
|
export const ACCOUNT_DEPROVISIONING_BANNER_DISK = new StateDefinition(
|
||||||
web: "disk-local",
|
"showAccountDeprovisioningBanner",
|
||||||
});
|
"disk",
|
||||||
|
{
|
||||||
|
web: "disk-local",
|
||||||
|
},
|
||||||
|
);
|
||||||
export const DELETE_MANAGED_USER_WARNING = new StateDefinition(
|
export const DELETE_MANAGED_USER_WARNING = new StateDefinition(
|
||||||
"showDeleteManagedUserWarning",
|
"showDeleteManagedUserWarning",
|
||||||
"disk",
|
"disk",
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { runMigrator } from "../migration-helper.spec";
|
||||||
|
import { IRREVERSIBLE } from "../migrator";
|
||||||
|
|
||||||
|
import { RemoveAcBannersDismissed } from "./70-remove-ac-banner-dismissed";
|
||||||
|
|
||||||
|
describe("RemoveAcBannersDismissed", () => {
|
||||||
|
const sut = new RemoveAcBannersDismissed(69, 70);
|
||||||
|
|
||||||
|
describe("migrate", () => {
|
||||||
|
it("deletes ac banner from all users", async () => {
|
||||||
|
const output = await runMigrator(sut, {
|
||||||
|
global_account_accounts: {
|
||||||
|
user1: {
|
||||||
|
email: "user1@email.com",
|
||||||
|
name: "User 1",
|
||||||
|
emailVerified: true,
|
||||||
|
},
|
||||||
|
user2: {
|
||||||
|
email: "user2@email.com",
|
||||||
|
name: "User 2",
|
||||||
|
emailVerified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_user1_showProviderClientVaultPrivacyBanner_acBannersDismissed: true,
|
||||||
|
user_user2_showProviderClientVaultPrivacyBanner_acBannersDismissed: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(output).toEqual({
|
||||||
|
global_account_accounts: {
|
||||||
|
user1: {
|
||||||
|
email: "user1@email.com",
|
||||||
|
name: "User 1",
|
||||||
|
emailVerified: true,
|
||||||
|
},
|
||||||
|
user2: {
|
||||||
|
email: "user2@email.com",
|
||||||
|
name: "User 2",
|
||||||
|
emailVerified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rollback", () => {
|
||||||
|
it("is irreversible", async () => {
|
||||||
|
await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||||
|
import { IRREVERSIBLE, Migrator } from "../migrator";
|
||||||
|
|
||||||
|
export const SHOW_BANNER_KEY: KeyDefinitionLike = {
|
||||||
|
key: "acBannersDismissed",
|
||||||
|
stateDefinition: { name: "showProviderClientVaultPrivacyBanner" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export class RemoveAcBannersDismissed extends Migrator<69, 70> {
|
||||||
|
async migrate(helper: MigrationHelper): Promise<void> {
|
||||||
|
await Promise.all(
|
||||||
|
(await helper.getAccounts()).map(async ({ userId }) => {
|
||||||
|
if (helper.getFromUser(userId, SHOW_BANNER_KEY) != null) {
|
||||||
|
await helper.removeFromUser(userId, SHOW_BANNER_KEY);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollback(helper: MigrationHelper): Promise<void> {
|
||||||
|
throw IRREVERSIBLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user