From b33abf036b90de5eb0a585c1b5c9975e825ad142 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 11 Nov 2025 12:52:49 +0100 Subject: [PATCH] chore: configure the 'maintenanceMode' flag to accept variants (#10956) Configure the `maintenanceMode` flag type to be `boolean | Variant` and update the env parsing to allow passing strings from the env. The [first impl](https://github.com/Unleash/unleash/pull/10956/commits/3bbfc9e68166f1dda3967504299ad97fa2d87b7e) required you to set a full, variant -- stringified as json -- in the env, but this is both error-prone and not very user friendly. Additionally, the name of the variant isn't really important, and if you're passing a string, you probably want it to be true. As such, the [second impl](https://github.com/Unleash/unleash/pull/10956/commits/c38357baa4c39fa44800b7fa25aafc2a1783ef94) updates the env parsing to read the full string value into a pre-formatted variant if it's not parseable as a boolean. As such, to set a custom message, you can now do: ```sh UNLEASH_EXPERIMENTAL_MAINTENANCE_MODE='Custom message from plain env var string' yarn dev ``` With the [updates to the UI](https://github.com/Unleash/unleash/pull/10961), it'll look a little something like this: image ## Rationale This allows locking down Unleash instances with a custom message. Previously, you'd have to use both maintenance mode and a custom banner for this, but that requires more work to set properly and it shows two banners, when you really only want the one. --- frontend/src/interfaces/uiConfig.ts | 2 +- src/lib/types/experimental.ts | 7 ++++-- src/lib/ui-config/ui-config-service.ts | 1 - src/lib/util/parseEnvVar.test.ts | 31 ++++++++++++++++++++++++++ src/lib/util/parseEnvVar.ts | 28 +++++++++++++++++++++++ 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 97480b58c0..bb87e8e65d 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -52,7 +52,7 @@ export type UiFlags = { T?: boolean; UNLEASH_CLOUD?: boolean; UG?: boolean; - maintenanceMode?: boolean; + maintenanceMode?: boolean | Variant; messageBanner?: Variant; banner?: Variant; notifications?: boolean; diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 21950ee639..820546d46b 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -1,6 +1,9 @@ import { PayloadType, type Variant } from 'unleash-client'; -import { parseEnvVarBoolean } from '../util/index.js'; import { defaultVariant } from 'unleash-client/lib/variant.js'; +import { + parseEnvVarBoolean, + parseEnvVarBooleanOrStringVariant, +} from '../util/index.js'; import type { MetricFlagContext } from 'unleash-client/lib/impact-metrics/metric-types.js'; import type { Context } from '../features/playground/feature-evaluator/index.js'; @@ -72,7 +75,7 @@ const flags: IFlags = { process.env.UNLEASH_RESPONSE_TIME_WITH_APP_NAME_KILL_SWITCH, false, ), - maintenanceMode: parseEnvVarBoolean( + maintenanceMode: parseEnvVarBooleanOrStringVariant( process.env.UNLEASH_EXPERIMENTAL_MAINTENANCE_MODE, false, ), diff --git a/src/lib/ui-config/ui-config-service.ts b/src/lib/ui-config/ui-config-service.ts index ae20dbe68d..9f1264f96b 100644 --- a/src/lib/ui-config/ui-config-service.ts +++ b/src/lib/ui-config/ui-config-service.ts @@ -108,7 +108,6 @@ export class UiConfigService { email: user.email, userId: user.id, }; - const uiConfig: UiConfigSchema = { ...this.config.ui, flags, diff --git a/src/lib/util/parseEnvVar.test.ts b/src/lib/util/parseEnvVar.test.ts index 1477b1af0b..1090f75828 100644 --- a/src/lib/util/parseEnvVar.test.ts +++ b/src/lib/util/parseEnvVar.test.ts @@ -1,5 +1,7 @@ +import { PayloadType } from 'unleash-client'; import { parseEnvVarBoolean, + parseEnvVarBooleanOrStringVariant, parseEnvVarNumber, parseEnvVarStrings, } from './parseEnvVar.js'; @@ -39,3 +41,32 @@ test('parseEnvVarStringList', () => { expect(parseEnvVarStrings('a,b,c', [])).toEqual(['a', 'b', 'c']); expect(parseEnvVarStrings(' a,,,b, c , ,', [])).toEqual(['a', 'b', 'c']); }); + +test('parseEnvVarBooleanOrStringVariant', () => { + expect(parseEnvVarBooleanOrStringVariant(undefined, true)).toEqual(true); + expect(parseEnvVarBooleanOrStringVariant(undefined, false)).toEqual(false); + for (const truthy of ['true', 't', '1']) { + expect(parseEnvVarBooleanOrStringVariant(truthy, false)).toEqual(true); + } + for (const falsy of ['false', 'f', '0']) { + expect(parseEnvVarBooleanOrStringVariant(falsy, true)).toEqual(false); + } + + expect( + parseEnvVarBooleanOrStringVariant(undefined, { + name: 'default-variant', + enabled: false, + }), + ).toEqual({ name: 'default-variant', enabled: false }); + + expect( + parseEnvVarBooleanOrStringVariant('custom string', true), + ).toMatchObject({ + name: expect.any(String), + enabled: true, + payload: { + value: 'custom string', + type: PayloadType.STRING, + }, + }); +}); diff --git a/src/lib/util/parseEnvVar.ts b/src/lib/util/parseEnvVar.ts index 1b9332192f..76c91f982d 100644 --- a/src/lib/util/parseEnvVar.ts +++ b/src/lib/util/parseEnvVar.ts @@ -1,3 +1,5 @@ +import { PayloadType, type Variant } from 'unleash-client'; + export function parseEnvVarNumber( envVar: string | undefined, defaultVal: number, @@ -53,3 +55,29 @@ export function parseEnvVarJSON( return defaultVal; } + +export function parseEnvVarBooleanOrStringVariant( + envVar: string | undefined, + defaultVal: boolean | Variant, +): boolean | Variant { + if (!envVar) { + return defaultVal; + } + + if (envVar === '1' || envVar === 't' || envVar === 'true') { + return true; + } + + if (envVar === '0' || envVar === 'f' || envVar === 'false') { + return false; + } + + return { + name: 'Variant', + enabled: true, + payload: { + type: PayloadType.STRING, + value: envVar, + }, + }; +}