1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 11:43:51 +00:00

Add feature flag to the changes

This commit is contained in:
Cy Okeke
2025-07-23 17:43:27 +01:00
parent 4c2e696b8c
commit 47fede2061
7 changed files with 80 additions and 79 deletions

View File

@@ -7,20 +7,20 @@
<a
bitButton
routerLink="create"
*ngIf="canAddClients"
*ngIf="manageOrganizations && isSuspensionActive"
buttonType="primary"
[title]="addClientTooltip"
[title]="addSuspensionTooltip"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newClient" | i18n }}
</a>
<button
*ngIf="manageOrganizations && !canAddClients"
*ngIf="manageOrganizations && isSuspensionActive"
type="button"
bitButton
buttonType="primary"
[disabled]="true"
[title]="addClientTooltip"
[title]="addSuspensionTooltip"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newClient" | i18n }}
@@ -28,8 +28,8 @@
<button
type="button"
bitButton
[disabled]="!canAddClients"
[title]="addClientTooltip"
[disabled]="isSuspensionActive"
[title]="addSuspensionTooltip"
(click)="addExistingOrganization()"
*ngIf="manageOrganizations && showAddExisting"
>

View File

@@ -18,6 +18,8 @@ import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/comm
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { PlanType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import {
@@ -60,26 +62,15 @@ export class ClientsComponent {
addableOrganizations: Organization[] = [];
loading = true;
manageOrganizations = false;
isAdminOrServiceUser = false;
showAddExisting = false;
dataSource: TableDataSource<ProviderOrganizationOrganizationDetailsResponse> =
new TableDataSource();
protected searchControl = new FormControl("", { nonNullable: true });
// Computed properties for provider suspension state
get isProviderDisabled(): boolean {
return !this.provider?.enabled;
}
get canAddClients(): boolean {
return this.manageOrganizations && !this.isProviderDisabled;
}
get addClientTooltip(): string {
if (this.isProviderDisabled) {
return this.i18nService.t("providerIsDisabled");
}
return "";
}
protected providerPortalTakeover$ = this.configService.getFeatureFlag$(
FeatureFlag.PM21821_ProviderPortalTakeover,
);
private providerPortalTakeoverEnabled = false;
constructor(
private router: Router,
@@ -94,17 +85,28 @@ export class ClientsComponent {
private toastService: ToastService,
private validationService: ValidationService,
private webProviderService: WebProviderService,
private configService: ConfigService,
) {
this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => {
this.searchControl.setValue(queryParams.search);
});
this.providerPortalTakeover$.pipe(takeUntilDestroyed()).subscribe((enabled) => {
this.providerPortalTakeoverEnabled = enabled;
});
this.activatedRoute.parent?.params
?.pipe(
switchMap((params) => {
this.providerId = params.providerId;
return this.providerService.get$(this.providerId).pipe(
map((provider) => provider?.providerStatus === ProviderStatusType.Billable),
map((provider) => {
this.provider = provider;
this.isAdminOrServiceUser =
provider.type === ProviderUserType.ProviderAdmin ||
provider.type === ProviderUserType.ServiceUser;
return provider?.providerStatus === ProviderStatusType.Billable;
}),
map((isBillable) => {
if (isBillable) {
return from(
@@ -159,11 +161,6 @@ export class ClientsComponent {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const clients = response.data != null && response.data.length > 0 ? response.data : [];
this.dataSource.data = clients;
// Store the provider for disabled state checks
this.provider = await this.providerService.get(this.providerId);
this.manageOrganizations = this.provider.type === ProviderUserType.ProviderAdmin;
const candidateOrgs = (
await firstValueFrom(this.organizationService.organizations$(userId))
).filter((o) => o.isOwner && o.providerId == null);
@@ -188,4 +185,14 @@ export class ClientsComponent {
await this.load();
}
}
get isSuspensionActive(): boolean {
return (
this.isAdminOrServiceUser && this.providerPortalTakeoverEnabled && !this.provider?.enabled
);
}
get addSuspensionTooltip(): string {
return this.isSuspensionActive ? this.i18nService.t("providerIsDisabled") : "";
}
}

View File

@@ -12,7 +12,7 @@
[route]="(isBillable | async) ? 'manage-client-organizations' : 'clients'"
>
<i
*ngIf="!provider.enabled"
*ngIf="!provider.enabled && (providerPortalTakeover$ | async)"
slot="end"
class="bwi bwi-exclamation-triangle tw-text-danger"
title="{{ 'providerIsDisabled' | i18n }}"

View File

@@ -35,6 +35,7 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
protected clientsTranslationKey$: Observable<string>;
protected managePaymentDetailsOutsideCheckout$: Observable<boolean>;
protected providerPortalTakeover$: Observable<boolean>;
constructor(
private route: ActivatedRoute,
@@ -77,6 +78,10 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
this.managePaymentDetailsOutsideCheckout$ = this.configService.getFeatureFlag$(
FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout,
);
this.providerPortalTakeover$ = this.configService.getFeatureFlag$(
FeatureFlag.PM21821_ProviderPortalTakeover,
);
}
ngOnDestroy() {

View File

@@ -5,9 +5,9 @@
buttonType="primary"
type="button"
[bitMenuTriggerFor]="clientMenu"
[disabled]="!canAddClients"
[title]="addClientTooltip"
appA11yTitle="{{ canAddClients ? ('add' | i18n) : addClientTooltip }}"
[disabled]="isSuspensionActive"
[title]="suspendedTooltip"
appA11yTitle="{{ isSuspensionActive ? ('add' | i18n) : suspendedTooltip }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "add" | i18n }}
@@ -16,8 +16,8 @@
<button
type="button"
bitMenuItem
[disabled]="!canAddClients"
[title]="addClientTooltip"
[disabled]="isSuspensionActive"
[title]="suspendedTooltip"
(click)="createClient()"
>
<i aria-hidden="true" class="bwi bwi-business"></i>
@@ -26,8 +26,8 @@
<button
type="button"
bitMenuItem
[disabled]="!canAddClients"
[title]="addClientTooltip"
[disabled]="isSuspensionActive"
[title]="suspendedTooltip"
(click)="addExistingOrganization()"
>
<i aria-hidden="true" class="bwi bwi-filter"></i>
@@ -90,8 +90,8 @@
<button
type="button"
bitMenuItem
[disabled]="!canManageClientNames"
[title]="manageClientNameTooltip"
[disabled]="isSuspensionActive"
[title]="suspendedTooltip"
(click)="manageClientName(row)"
>
<i aria-hidden="true" class="bwi bwi-pencil-square"></i>
@@ -100,14 +100,13 @@
<button
type="button"
bitMenuItem
[disabled]="!canManageClientSubscriptions"
[title]="manageSubscriptionTooltip"
[disabled]="isSuspensionActive"
[title]="suspendedTooltip"
(click)="manageClientSubscription(row)"
>
<i aria-hidden="true" class="bwi bwi-family"></i>
{{ "manageSubscription" | i18n }}
</button>
<!-- Keep unlink enabled even when provider is suspended -->
<button *ngIf="isProviderAdmin" type="button" bitMenuItem (click)="remove(row)">
<span class="tw-text-danger">
<i aria-hidden="true" class="bwi bwi-close"></i> {{ "unlinkOrganization" | i18n }}
@@ -119,7 +118,7 @@
</bit-table-scroll>
<div *ngIf="dataSource.data.length === 0" class="tw-mt-10">
<app-no-clients
[showAddOrganizationButton]="canAddClients"
[showAddOrganizationButton]="isSuspensionActive"
(addNewOrganizationClicked)="createClient()"
/>
</div>

View File

@@ -15,6 +15,8 @@ import { Provider } from "@bitwarden/common/admin-console/models/domain/provider
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import {
@@ -65,6 +67,7 @@ export class ManageClientsComponent {
provider: Provider | undefined;
loading = true;
isProviderAdmin = false;
isServiceUser = false;
dataSource: TableDataSource<ProviderOrganizationOrganizationDetailsResponse> =
new TableDataSource();
@@ -75,43 +78,10 @@ export class ManageClientsComponent {
clientColumnHeader = this.i18nService.t("client");
newClientButtonLabel = this.i18nService.t("newClient");
// Computed properties for provider suspension state
get isProviderDisabled(): boolean {
return !this.provider?.enabled;
}
get canAddClients(): boolean {
return this.isProviderAdmin && !this.isProviderDisabled;
}
get canManageClientNames(): boolean {
return this.isProviderAdmin && !this.isProviderDisabled;
}
get canManageClientSubscriptions(): boolean {
return this.isProviderAdmin && !this.isProviderDisabled;
}
get addClientTooltip(): string {
if (this.isProviderDisabled) {
return this.i18nService.t("providerIsDisabled");
}
return "";
}
get manageClientNameTooltip(): string {
if (this.isProviderDisabled) {
return this.i18nService.t("providerIsDisabled");
}
return "";
}
get manageSubscriptionTooltip(): string {
if (this.isProviderDisabled) {
return this.i18nService.t("providerIsDisabled");
}
return "";
}
protected providerPortalTakeover$ = this.configService.getFeatureFlag$(
FeatureFlag.PM21821_ProviderPortalTakeover,
);
private providerPortalTakeoverEnabled = false;
constructor(
private billingApiService: BillingApiServiceAbstraction,
@@ -124,11 +94,16 @@ export class ManageClientsComponent {
private validationService: ValidationService,
private webProviderService: WebProviderService,
private billingNotificationService: BillingNotificationService,
private configService: ConfigService,
) {
this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => {
this.searchControl.setValue(queryParams.search);
});
this.providerPortalTakeover$.pipe(takeUntilDestroyed()).subscribe((enabled) => {
this.providerPortalTakeoverEnabled = enabled;
});
this.activatedRoute.parent?.params
?.pipe(
switchMap((params) => {
@@ -169,6 +144,7 @@ export class ManageClientsComponent {
this.newClientButtonLabel = this.i18nService.t("newBusinessUnit");
}
this.isProviderAdmin = this.provider?.type === ProviderUserType.ProviderAdmin;
this.isServiceUser = this.provider?.type === ProviderUserType.ServiceUser;
this.dataSource.data = (
await this.billingApiService.getProviderClientOrganizations(this.providerId)
).data;
@@ -269,4 +245,16 @@ export class ManageClientsComponent {
this.validationService.showError(e);
}
}
get isSuspensionActive(): boolean {
return (
(this.isProviderAdmin || this.isServiceUser) &&
this.providerPortalTakeoverEnabled &&
!this.provider?.enabled
);
}
get suspendedTooltip(): string {
return this.isSuspensionActive ? this.i18nService.t("providerIsDisabled") : "";
}
}

View File

@@ -34,6 +34,7 @@ export enum FeatureFlag {
UseOrganizationWarningsService = "use-organization-warnings-service",
AllowTrialLengthZero = "pm-20322-allow-trial-length-0",
PM21881_ManagePaymentDetailsOutsideCheckout = "pm-21881-manage-payment-details-outside-checkout",
PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover",
/* Data Insights and Reporting */
EnableRiskInsightsNotifications = "enable-risk-insights-notifications",
@@ -117,6 +118,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.UseOrganizationWarningsService]: FALSE,
[FeatureFlag.AllowTrialLengthZero]: FALSE,
[FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout]: FALSE,
[FeatureFlag.PM21821_ProviderPortalTakeover]: FALSE,
/* Key Management */
[FeatureFlag.PrivateKeyRegeneration]: FALSE,