From 749304e562132a30f37507be07ba77b1965b5b63 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 14 Jul 2025 15:25:37 -0400 Subject: [PATCH] PM-23733 - Add filter + force refresh --- apps/web/src/locales/en/messages.json | 6 +++ .../feature-flags.component.html | 38 ++++++++++++------- .../feature-flags/feature-flags.component.ts | 33 +++++++++++++++- .../abstractions/config/config.service.ts | 5 +++ .../services/config/default-config.service.ts | 22 ++++++++--- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 00fb0e9806b..cd19e083008 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -2099,6 +2099,12 @@ "featureFlags": { "message": "Feature flags" }, + "flagName": { + "message": "Flag name" + }, + "flagValue": { + "message": "Flag value" + }, "domainRules": { "message": "Domain rules" }, diff --git a/libs/angular/src/platform/feature-flags/feature-flags.component.html b/libs/angular/src/platform/feature-flags/feature-flags.component.html index e91f2abbbc4..550ef60efd5 100644 --- a/libs/angular/src/platform/feature-flags/feature-flags.component.html +++ b/libs/angular/src/platform/feature-flags/feature-flags.component.html @@ -3,20 +3,30 @@ } @else { - - + - - - key - value - + - - - {{ r.key }} - {{ r.value }} - - - +
+ + + {{ "flagName" | i18n }} + {{ "flagValue" | i18n }} + + + + + {{ r.key }} + {{ r.value }} + + + +
} diff --git a/libs/angular/src/platform/feature-flags/feature-flags.component.ts b/libs/angular/src/platform/feature-flags/feature-flags.component.ts index e4d4d8eb5c9..70f7036fb44 100644 --- a/libs/angular/src/platform/feature-flags/feature-flags.component.ts +++ b/libs/angular/src/platform/feature-flags/feature-flags.component.ts @@ -1,21 +1,41 @@ import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormsModule } from "@angular/forms"; import { map } from "rxjs"; import { AllowedFeatureFlagTypes } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { TableDataSource, TableModule } from "@bitwarden/components"; +import { + ButtonModule, + FormFieldModule, + InputModule, + SearchModule, + TableDataSource, + TableModule, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "app-feature-flags", templateUrl: "./feature-flags.component.html", - imports: [CommonModule, TableModule], + imports: [ + CommonModule, + TableModule, + ButtonModule, + I18nPipe, + FormsModule, + FormFieldModule, + InputModule, + SearchModule, + ], }) export class FeatureFlagsComponent implements OnInit { loading = true; tableDataSource = new TableDataSource<{ key: string; value: AllowedFeatureFlagTypes }>(); + searchText = ""; + constructor( private destroyRef: DestroyRef, private configService: ConfigService, @@ -42,4 +62,13 @@ export class FeatureFlagsComponent implements OnInit { this.loading = false; }); } + + onSearchTextChanged(searchText: string) { + this.searchText = searchText; + this.tableDataSource.filter = searchText; + } + + refresh() { + this.configService.refreshServerConfig(); + } } diff --git a/libs/common/src/platform/abstractions/config/config.service.ts b/libs/common/src/platform/abstractions/config/config.service.ts index b24cb6f7282..c378ea7461f 100644 --- a/libs/common/src/platform/abstractions/config/config.service.ts +++ b/libs/common/src/platform/abstractions/config/config.service.ts @@ -60,4 +60,9 @@ export abstract class ConfigService { * Triggers a check that the config for the currently active user is up-to-date. If it is not, it will be fetched from the server and stored. */ abstract ensureConfigFetched(): Promise; + + /** + * Refreshes the server config, forcing a new retrieval from the server. + */ + abstract refreshServerConfig(): void; } diff --git a/libs/common/src/platform/services/config/default-config.service.ts b/libs/common/src/platform/services/config/default-config.service.ts index 8e36341aa8f..a0c826a8f01 100644 --- a/libs/common/src/platform/services/config/default-config.service.ts +++ b/libs/common/src/platform/services/config/default-config.service.ts @@ -9,6 +9,7 @@ import { Observable, of, shareReplay, + startWith, Subject, switchMap, tap, @@ -57,6 +58,7 @@ export const GLOBAL_SERVER_CONFIGURATIONS = KeyDefinition.record(); + private manualRefresh$ = new Subject(); serverConfig$: Observable; @@ -82,22 +84,28 @@ export class DefaultConfigService implements ConfigService { userId$, this.environmentService.environment$, authStatus$, + // must have default value so combineLatest will emit once others have emitted + this.manualRefresh$.pipe(startWith(false)), ]).pipe( - switchMap(([userId, environment, authStatus]) => { + switchMap(([userId, environment, authStatus, manualRefresh]) => { if (userId == null || authStatus !== AuthenticationStatus.Unlocked) { return this.globalConfigFor$(environment.getApiUrl()).pipe( - map((config) => [config, null, environment] as const), + map((config) => [config, null, environment, manualRefresh] as const), ); } return this.userConfigFor$(userId).pipe( - map((config) => [config, userId, environment] as const), + map((config) => [config, userId, environment, manualRefresh] as const), ); }), tap(async (rec) => { - const [existingConfig, userId, environment] = rec; + const [existingConfig, userId, environment, manualRefresh] = rec; // Grab new config if older retrieval interval - if (!existingConfig || this.olderThanRetrievalInterval(existingConfig.utcDate)) { + if ( + !existingConfig || + this.olderThanRetrievalInterval(existingConfig.utcDate) || + manualRefresh + ) { await this.renewConfig(existingConfig, userId, environment); } }), @@ -128,6 +136,10 @@ export class DefaultConfigService implements ConfigService { ); } + refreshServerConfig() { + this.manualRefresh$.next(); + } + getFeatureFlag$(key: Flag) { return this.serverConfig$.pipe(map((serverConfig) => getFeatureFlagValue(serverConfig, key))); }