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..1a19d1bb6c 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -47,6 +47,34 @@ export type DefaultConfig = { // @public export type EvaluationContext = UserProvidedSignals & PredefinedSignals; +// @public +export interface ExperimentParameterValue { + experimentValue: ExperimentValue; +} + +// @public +export interface ExperimentValue { + experimentId: string; + variantValue: ExperimentVariantValue[]; +} + +// @public +export interface ExperimentVariantExplicitValue { + noChange?: never; + value: string; + variantId: string; +} + +// @public +export interface ExperimentVariantNoChange { + noChange: true; + value?: never; + variantId: string; +} + +// @public +export type ExperimentVariantValue = ExperimentVariantExplicitValue | ExperimentVariantNoChange; + // @public export interface ExplicitParameterValue { value: string; @@ -142,6 +170,16 @@ export enum PercentConditionOperator { UNKNOWN = "UNKNOWN" } +// @public +export interface PersonalizationParameterValue { + personalizationValue: PersonalizationValue; +} + +// @public +export interface PersonalizationValue { + personalizationId: string; +} + // @public export type PredefinedSignals = { randomizationId?: string; @@ -197,7 +235,7 @@ export interface RemoteConfigParameterGroup { } // @public -export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; +export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue | RolloutParameterValue | PersonalizationParameterValue | ExperimentParameterValue; // @public export interface RemoteConfigTemplate { @@ -219,6 +257,18 @@ export interface RemoteConfigUser { name?: string; } +// @public +export interface RolloutParameterValue { + rolloutValue: RolloutValue; +} + +// @public +export interface RolloutValue { + percent: number; + rolloutId: string; + value: string; +} + // @public export interface ServerConfig { getAll(): { diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index 790568735e..e44606f193 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -47,6 +47,15 @@ export { PredefinedSignals, RemoteConfigCondition, RemoteConfigParameter, + ExperimentParameterValue, + ExperimentValue, + ExperimentVariantExplicitValue, + ExperimentVariantNoChange, + ExperimentVariantValue, + PersonalizationParameterValue, + PersonalizationValue, + RolloutParameterValue, + RolloutValue, RemoteConfigParameterGroup, RemoteConfigParameterValue, RemoteConfigTemplate, diff --git a/src/remote-config/remote-config-api.ts b/src/remote-config/remote-config-api.ts index ed7da05496..49b144b69c 100644 --- a/src/remote-config/remote-config-api.ts +++ b/src/remote-config/remote-config-api.ts @@ -354,12 +354,146 @@ export interface InAppDefaultValue { useInAppDefault: boolean; } +/** + * Interface representing a value that is linked to a Rollout. + */ +export interface RolloutValue { + /** + * The ID of the Rollout to which the value is linked. + */ + rolloutId: string; + + /** + * The value that is being rolled out. + */ + value: string; + + /** + * The rollout percentage representing the exposure of the rollout value in + * the target audience. + */ + percent: number; // Numeric value between 1-100 +} + +/** + * Interface representing a value returned by Personalization. + */ +export interface PersonalizationValue { + /** + * The ID of the Personalization to which the value is linked. + */ + personalizationId: string; +} + +/** + * Interface representing a specific variant value within an Experiment. + */ +export interface ExperimentVariantExplicitValue { + /** + * ID of the variant value within an Experiment. + */ + variantId: string; + + /** + * Value of the variant within an Experiment. + */ + value: string; + + /** + * Represents an unset `noChange` value. To set `noChange`, use + * `ExperimentVariantNoChange` instead. + */ + noChange?: never; +} + +/** + * Represents a no-change variant value within an Experiment. + */ +export interface ExperimentVariantNoChange { + /** + * ID of the variant value within an Experiment. + */ + variantId: string; + + /** + * Represents an unset value as only one of `noChange` or `value` can be set. + * To set a variant value, use `ExperimentVariantExplicitValue` instead. + */ + value?: never; + + /** + * Represents a no-change variant value within an Experiment. If `true`, + * the variant served to the client is equal to the value against the + * next condition in the evaluation order (or the default value if no + * conditions are applicable). + */ + noChange: true; +} + +/** + * Type representing a Experiment variant value. + * A `ExperimentVariantValue` could be either an + * `ExperimentVariantExplicitValue` or an `ExperimentVariantNoChange`. + */ +export type ExperimentVariantValue = ExperimentVariantExplicitValue | ExperimentVariantNoChange; + +/** + * Represents an Experiment value. + */ +export interface ExperimentValue { + /** + * ID of the Experiment to which the value is linked. + */ + experimentId: string; + + /** + * Collection of `ExperimentVariantValue`s that represents the variants + * served by the Experiment. + */ + variantValue: ExperimentVariantValue[]; +} + +/** + * Interface representing a parameter value linked to a Rollout. + */ +export interface RolloutParameterValue { + /** + * The value returned by a Rollout. + */ + rolloutValue: RolloutValue; +} + +/** + * Interface representing a parameter value linked to a Personalization. + */ +export interface PersonalizationParameterValue { + /** + * The value returned by a Personalization. + */ + personalizationValue: PersonalizationValue; +} + +/** + * Interface representing a parameter value linked to an Experiment. + */ +export interface ExperimentParameterValue { + /** + * The value returned by an Experiment. + */ + 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/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; } diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index e7e4f84e57..74d277290e 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'; @@ -96,6 +99,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 +198,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', @@ -542,6 +629,35 @@ 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' }); + expect(p2.conditionalValues).to.not.be.undefined; + 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!' }); + expect(p3.conditionalValues).to.not.be.undefined; + 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' }); + expect(p4.conditionalValues).to.not.be.undefined; + 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');