From 881b4b929b74f485e31dff588d8cc48b8783b2c7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 13:25:50 +0530 Subject: [PATCH 1/5] Support Rollout, Personalization, and Experiment values in Remote Config (#3042) * feat(remote-config): Support Rollout, Personalization, and Experiment values Added new Remote Config parameter value types (Rollout, Personalization, Experiment) to the SDK. Updated `RemoteConfigParameterValue` union type and added corresponding interfaces. Updated unit tests to verify the new types in Remote Config templates. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/remote-config/remote-config-api.ts | 63 +++++++++++++- src/remote-config/remote-config-namespace.ts | 54 ++++++++++++ test/unit/remote-config/remote-config.spec.ts | 84 +++++++++++++++++++ 3 files changed, 200 insertions(+), 1 deletion(-) diff --git a/src/remote-config/remote-config-api.ts b/src/remote-config/remote-config-api.ts index ed7da05496..e34a7d8317 100644 --- a/src/remote-config/remote-config-api.ts +++ b/src/remote-config/remote-config-api.ts @@ -354,12 +354,73 @@ export interface InAppDefaultValue { useInAppDefault: boolean; } +/** + * Represents a Rollout value. + */ +export interface RolloutValue { + rolloutId: string; + value: string; + percent: number; // Numeric value between 1-100 +} + +/** + * Represents a Personalization value. + */ +export interface PersonalizationValue { + personalizationId: string; +} + +/** + * Represents a specific variant value within an Experiment. + */ +export interface ExperimentVariantExplicitValue { + variantId: string; + value: string; + noChange?: never; +} + +/** + * Represents a no-change variant value within an Experiment. + */ +export interface ExperimentVariantNoChange { + variantId: string; + value?: never; + noChange: true; +} + +export type ExperimentVariantValue = ExperimentVariantExplicitValue | ExperimentVariantNoChange; + +/** + * Represents an Experiment value. + */ +export interface ExperimentValue { + experimentId: string; + variantValue: ExperimentVariantValue[]; +} + +export interface RolloutParameterValue { + rolloutValue: RolloutValue; +} + +export interface PersonalizationParameterValue { + personalizationValue: PersonalizationValue; +} + +export interface ExperimentParameterValue { + experimentValue: ExperimentValue; +} + /** * Type representing a Remote Config parameter value. * A `RemoteConfigParameterValue` could be either an `ExplicitParameterValue` or * an `InAppDefaultValue`. */ -export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; +export type RemoteConfigParameterValue = + | ExplicitParameterValue + | InAppDefaultValue + | RolloutParameterValue + | PersonalizationParameterValue + | ExperimentParameterValue; /** * Interface representing a Remote Config parameter. diff --git a/src/remote-config/remote-config-namespace.ts b/src/remote-config/remote-config-namespace.ts index 159204d316..abc315b150 100644 --- a/src/remote-config/remote-config-namespace.ts +++ b/src/remote-config/remote-config-namespace.ts @@ -18,6 +18,15 @@ import { App } from '../app'; import { ExplicitParameterValue as TExplicitParameterValue, InAppDefaultValue as TInAppDefaultValue, + RolloutValue as TRolloutValue, + PersonalizationValue as TPersonalizationValue, + ExperimentVariantExplicitValue as TExperimentVariantExplicitValue, + ExperimentVariantNoChange as TExperimentVariantNoChange, + ExperimentVariantValue as TExperimentVariantValue, + ExperimentValue as TExperimentValue, + RolloutParameterValue as TRolloutParameterValue, + PersonalizationParameterValue as TPersonalizationParameterValue, + ExperimentParameterValue as TExperimentParameterValue, ListVersionsOptions as TListVersionsOptions, ListVersionsResult as TListVersionsResult, ParameterValueType as TParameterValueType, @@ -73,6 +82,51 @@ export namespace remoteConfig { */ export type InAppDefaultValue = TInAppDefaultValue; + /** + * Type alias to {@link firebase-admin.remote-config#RolloutValue}. + */ + export type RolloutValue = TRolloutValue; + + /** + * Type alias to {@link firebase-admin.remote-config#PersonalizationValue}. + */ + export type PersonalizationValue = TPersonalizationValue; + + /** + * Type alias to {@link firebase-admin.remote-config#ExperimentVariantExplicitValue}. + */ + export type ExperimentVariantExplicitValue = TExperimentVariantExplicitValue; + + /** + * Type alias to {@link firebase-admin.remote-config#ExperimentVariantNoChange}. + */ + export type ExperimentVariantNoChange = TExperimentVariantNoChange; + + /** + * Type alias to {@link firebase-admin.remote-config#ExperimentVariantValue}. + */ + export type ExperimentVariantValue = TExperimentVariantValue; + + /** + * Type alias to {@link firebase-admin.remote-config#ExperimentValue}. + */ + export type ExperimentValue = TExperimentValue; + + /** + * Type alias to {@link firebase-admin.remote-config#RolloutParameterValue}. + */ + export type RolloutParameterValue = TRolloutParameterValue; + + /** + * Type alias to {@link firebase-admin.remote-config#PersonalizationParameterValue}. + */ + export type PersonalizationParameterValue = TPersonalizationParameterValue; + + /** + * Type alias to {@link firebase-admin.remote-config#ExperimentParameterValue}. + */ + export type ExperimentParameterValue = TExperimentParameterValue; + /** * Type alias to {@link firebase-admin.remote-config#ListVersionsOptions}. */ diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index e7e4f84e57..f7a2bcdbc5 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -96,6 +96,48 @@ describe('RemoteConfig', () => { description: 'this is a promo', valueType: 'BOOLEAN', }, + new_ui_enabled: { + defaultValue: { value: 'false' }, + conditionalValues: { + ios: { + rolloutValue: { + rolloutId: 'rollout_1', + value: 'true', + percent: 50, + } + } + }, + description: 'New UI Rollout', + valueType: 'BOOLEAN', + }, + personalized_welcome_message: { + defaultValue: { value: 'Welcome!' }, + conditionalValues: { + ios: { + personalizationValue: { + personalizationId: 'personalization_1', + } + } + }, + description: 'Personalized Welcome Message', + valueType: 'STRING', + }, + experiment_enabled: { + defaultValue: { value: 'false' }, + conditionalValues: { + ios: { + experimentValue: { + experimentId: 'experiment_1', + variantValue: [ + { variantId: 'variant_A', value: 'true' }, + { variantId: 'variant_B', noChange: true } + ] + } + } + }, + description: 'Experiment Enabled', + valueType: 'BOOLEAN', + } }, parameterGroups: PARAMETER_GROUPS, etag: 'etag-123456789012-5', @@ -153,6 +195,48 @@ describe('RemoteConfig', () => { description: 'this is a promo', valueType: 'BOOLEAN', }, + new_ui_enabled: { + defaultValue: { value: 'false' }, + conditionalValues: { + ios: { + rolloutValue: { + rolloutId: 'rollout_1', + value: 'true', + percent: 50, + } + } + }, + description: 'New UI Rollout', + valueType: 'BOOLEAN', + }, + personalized_welcome_message: { + defaultValue: { value: 'Welcome!' }, + conditionalValues: { + ios: { + personalizationValue: { + personalizationId: 'personalization_1', + } + } + }, + description: 'Personalized Welcome Message', + valueType: 'STRING', + }, + experiment_enabled: { + defaultValue: { value: 'false' }, + conditionalValues: { + ios: { + experimentValue: { + experimentId: 'experiment_1', + variantValue: [ + { variantId: 'variant_A', value: 'true' }, + { variantId: 'variant_B', noChange: true } + ] + } + } + }, + description: 'Experiment Enabled', + valueType: 'BOOLEAN', + } }, parameterGroups: PARAMETER_GROUPS, etag: 'etag-123456789012-6', From 1df9cc3fbe3752a58d0de0ddcb101d524e933584 Mon Sep 17 00:00:00 2001 From: Ashish Kothari Date: Wed, 31 Dec 2025 12:24:34 +0530 Subject: [PATCH 2/5] Address issue with server templates evaluating unsupported values --- src/remote-config/remote-config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index 91eebfa0cb..6ae44c4064 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -382,6 +382,7 @@ class ServerTemplateImpl implements ServerTemplate { // Iterates in order over condition list. If there is a value associated // with a condition, this checks if the condition is true. for (const [conditionName, conditionEvaluation] of evaluatedConditions) { + if (normalizedConditionalValues[conditionName] && conditionEvaluation) { parameterValueWrapper = normalizedConditionalValues[conditionName]; break; @@ -395,7 +396,9 @@ class ServerTemplateImpl implements ServerTemplate { if (parameterValueWrapper) { const parameterValue = (parameterValueWrapper as ExplicitParameterValue).value; - configValues[key] = new ValueImpl('remote', parameterValue); + if (parameterValue !== undefined) { + configValues[key] = new ValueImpl('remote', parameterValue); + } continue; } From 85c5cd10a80ec0c5e8a7ec8a8284f8cbfccfad5c Mon Sep 17 00:00:00 2001 From: Ashish Kothari Date: Wed, 31 Dec 2025 12:34:24 +0530 Subject: [PATCH 3/5] Generate apidocs --- etc/firebase-admin.api.md | 18 ++++++++++++++++++ etc/firebase-admin.remote-config.api.md | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index e9d4bb0e49..e4675c2009 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -434,6 +434,16 @@ export function remoteConfig(app?: App): remoteConfig.RemoteConfig; // @public (undocumented) export namespace remoteConfig { + // Warning: (ae-forgotten-export) The symbol "ExperimentParameterValue" needs to be exported by the entry point default-namespace.d.ts + export type ExperimentParameterValue = ExperimentParameterValue; + // Warning: (ae-forgotten-export) The symbol "ExperimentValue" needs to be exported by the entry point default-namespace.d.ts + export type ExperimentValue = ExperimentValue; + // Warning: (ae-forgotten-export) The symbol "ExperimentVariantExplicitValue" needs to be exported by the entry point default-namespace.d.ts + export type ExperimentVariantExplicitValue = ExperimentVariantExplicitValue; + // Warning: (ae-forgotten-export) The symbol "ExperimentVariantNoChange" needs to be exported by the entry point default-namespace.d.ts + export type ExperimentVariantNoChange = ExperimentVariantNoChange; + // Warning: (ae-forgotten-export) The symbol "ExperimentVariantValue" needs to be exported by the entry point default-namespace.d.ts + export type ExperimentVariantValue = ExperimentVariantValue; // Warning: (ae-forgotten-export) The symbol "ExplicitParameterValue" needs to be exported by the entry point default-namespace.d.ts export type ExplicitParameterValue = ExplicitParameterValue; // Warning: (ae-forgotten-export) The symbol "InAppDefaultValue" needs to be exported by the entry point default-namespace.d.ts @@ -444,6 +454,10 @@ export namespace remoteConfig { export type ListVersionsResult = ListVersionsResult; // Warning: (ae-forgotten-export) The symbol "ParameterValueType" needs to be exported by the entry point default-namespace.d.ts export type ParameterValueType = ParameterValueType; + // Warning: (ae-forgotten-export) The symbol "PersonalizationParameterValue" needs to be exported by the entry point default-namespace.d.ts + export type PersonalizationParameterValue = PersonalizationParameterValue; + // Warning: (ae-forgotten-export) The symbol "PersonalizationValue" needs to be exported by the entry point default-namespace.d.ts + export type PersonalizationValue = PersonalizationValue; // Warning: (ae-forgotten-export) The symbol "RemoteConfig" needs to be exported by the entry point default-namespace.d.ts export type RemoteConfig = RemoteConfig; // Warning: (ae-forgotten-export) The symbol "RemoteConfigCondition" needs to be exported by the entry point default-namespace.d.ts @@ -458,6 +472,10 @@ export namespace remoteConfig { export type RemoteConfigTemplate = RemoteConfigTemplate; // Warning: (ae-forgotten-export) The symbol "RemoteConfigUser" needs to be exported by the entry point default-namespace.d.ts export type RemoteConfigUser = RemoteConfigUser; + // Warning: (ae-forgotten-export) The symbol "RolloutParameterValue" needs to be exported by the entry point default-namespace.d.ts + export type RolloutParameterValue = RolloutParameterValue; + // Warning: (ae-forgotten-export) The symbol "RolloutValue" needs to be exported by the entry point default-namespace.d.ts + export type RolloutValue = RolloutValue; // Warning: (ae-forgotten-export) The symbol "TagColor" needs to be exported by the entry point default-namespace.d.ts export type TagColor = TagColor; // Warning: (ae-forgotten-export) The symbol "Version" needs to be exported by the entry point default-namespace.d.ts diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index cf7eb32b93..505eb3ada5 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -196,8 +196,12 @@ export interface RemoteConfigParameterGroup { }; } +// Warning: (ae-forgotten-export) The symbol "RolloutParameterValue" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "PersonalizationParameterValue" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ExperimentParameterValue" needs to be exported by the entry point index.d.ts +// // @public -export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; +export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue | RolloutParameterValue | PersonalizationParameterValue | ExperimentParameterValue; // @public export interface RemoteConfigTemplate { From 802ab0dfc801bf8036b2943b3681f5fcba6a3c79 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:50:22 +0530 Subject: [PATCH 4/5] Add assertions for new Remote Config value types in createTemplateFromJSON test (#3047) * Add assertions for new Remote Config value types in createTemplateFromJSON test - Updates the `createTemplateFromJSON` unit test to include specific assertions for `new_ui_enabled`, `personalized_welcome_message`, and `experiment_enabled` parameters. This ensures that Rollout, Personalization, and Experiment value types are correctly handled when creating a template from a JSON string. - Exports the necessary interfaces from `src/remote-config/index.ts` to support these assertions. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/remote-config/index.ts | 3 ++ test/unit/remote-config/remote-config.spec.ts | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index 790568735e..f6121c60ac 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -47,6 +47,9 @@ export { PredefinedSignals, RemoteConfigCondition, RemoteConfigParameter, + ExperimentParameterValue, + PersonalizationParameterValue, + RolloutParameterValue, RemoteConfigParameterGroup, RemoteConfigParameterValue, RemoteConfigTemplate, diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index f7a2bcdbc5..6d501aeb4b 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -27,6 +27,9 @@ import { TagColor, ListVersionsResult, RemoteConfigFetchResponse, + RolloutParameterValue, + PersonalizationParameterValue, + ExperimentParameterValue, } from '../../../src/remote-config/index'; import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; @@ -626,6 +629,32 @@ describe('RemoteConfig', () => { expect(p1.description).equals('this is a promo'); expect(p1.valueType).equals('BOOLEAN'); + const p2 = newTemplate.parameters['new_ui_enabled']; + expect(p2.defaultValue).deep.equals({ value: 'false' }); + const rolloutParam = p2.conditionalValues['ios'] as RolloutParameterValue; + expect(rolloutParam.rolloutValue.rolloutId).to.equal('rollout_1'); + expect(rolloutParam.rolloutValue.value).to.equal('true'); + expect(rolloutParam.rolloutValue.percent).to.equal(50); + expect(p2.description).equals('New UI Rollout'); + expect(p2.valueType).equals('BOOLEAN'); + + const p3 = newTemplate.parameters['personalized_welcome_message']; + expect(p3.defaultValue).deep.equals({ value: 'Welcome!' }); + const personalizationParam = p3.conditionalValues['ios'] as PersonalizationParameterValue; + expect(personalizationParam.personalizationValue.personalizationId).to.equal('personalization_1'); + expect(p3.description).equals('Personalized Welcome Message'); + expect(p3.valueType).equals('STRING'); + + const p4 = newTemplate.parameters['experiment_enabled']; + expect(p4.defaultValue).deep.equals({ value: 'false' }); + const experimentParam = p4.conditionalValues['ios'] as ExperimentParameterValue; + expect(experimentParam.experimentValue.experimentId).to.equal('experiment_1'); + expect(experimentParam.experimentValue.variantValue.length).to.equal(2); + expect(experimentParam.experimentValue.variantValue[0]).to.deep.equal({ variantId: 'variant_A', value: 'true' }); + expect(experimentParam.experimentValue.variantValue[1]).to.deep.equal({ variantId: 'variant_B', noChange: true }); + expect(p4.description).equals('Experiment Enabled'); + expect(p4.valueType).equals('BOOLEAN'); + expect(newTemplate.parameterGroups).deep.equals(PARAMETER_GROUPS); const c = newTemplate.conditions.find((c) => c.name === 'ios'); From dea5828745a49a737acc90ed9a4a808574d2d23f Mon Sep 17 00:00:00 2001 From: Ashish Kothari Date: Wed, 31 Dec 2025 16:55:41 +0530 Subject: [PATCH 5/5] Generate apidocs again --- etc/firebase-admin.remote-config.api.md | 28 +++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index 505eb3ada5..4bbc0671ee 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -47,6 +47,14 @@ export type DefaultConfig = { // @public export type EvaluationContext = UserProvidedSignals & PredefinedSignals; +// @public (undocumented) +export interface ExperimentParameterValue { + // Warning: (ae-forgotten-export) The symbol "ExperimentValue" needs to be exported by the entry point index.d.ts + // + // (undocumented) + experimentValue: ExperimentValue; +} + // @public export interface ExplicitParameterValue { value: string; @@ -142,6 +150,14 @@ export enum PercentConditionOperator { UNKNOWN = "UNKNOWN" } +// @public (undocumented) +export interface PersonalizationParameterValue { + // Warning: (ae-forgotten-export) The symbol "PersonalizationValue" needs to be exported by the entry point index.d.ts + // + // (undocumented) + personalizationValue: PersonalizationValue; +} + // @public export type PredefinedSignals = { randomizationId?: string; @@ -196,10 +212,6 @@ export interface RemoteConfigParameterGroup { }; } -// Warning: (ae-forgotten-export) The symbol "RolloutParameterValue" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "PersonalizationParameterValue" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "ExperimentParameterValue" needs to be exported by the entry point index.d.ts -// // @public export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue | RolloutParameterValue | PersonalizationParameterValue | ExperimentParameterValue; @@ -223,6 +235,14 @@ export interface RemoteConfigUser { name?: string; } +// @public (undocumented) +export interface RolloutParameterValue { + // Warning: (ae-forgotten-export) The symbol "RolloutValue" needs to be exported by the entry point index.d.ts + // + // (undocumented) + rolloutValue: RolloutValue; +} + // @public export interface ServerConfig { getAll(): {