Skip to content

Commit

Permalink
CR changes
Browse files Browse the repository at this point in the history
  • Loading branch information
greghuels committed Sep 30, 2024
1 parent 0a9f095 commit 5e5ea30
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 24 deletions.
20 changes: 10 additions & 10 deletions src/client/eppo-client-experiment-container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import * as applicationLogger from '../application-logger';
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store';
import { Flag, ObfuscatedFlag } from '../interfaces';

import EppoClient, { IFlagExperiment } from './eppo-client';
import EppoClient, { IContainerExperiment } from './eppo-client';
import { initConfiguration } from './test-utils';

type Container = { name: string };

describe('getExperimentContainer', () => {
describe('getExperimentContainerEntry', () => {
global.fetch = jest.fn(() => {
const ufc = readMockUFCResponse(MOCK_UFC_RESPONSE_FILE);
return Promise.resolve({
Expand All @@ -24,7 +24,7 @@ describe('getExperimentContainer', () => {
const treatment3Container: Container = { name: 'Treatment Variation 3 Container' };

let client: EppoClient;
let flagExperiment: IFlagExperiment<Container>;
let flagExperiment: IContainerExperiment<Container>;
let getStringAssignmentSpy: jest.SpyInstance;
let loggerWarnSpy: jest.SpyInstance;

Expand All @@ -35,8 +35,8 @@ describe('getExperimentContainer', () => {
client.setIsGracefulFailureMode(true);
flagExperiment = {
flagKey: 'my-key',
controlVariation: controlContainer,
treatmentVariations: [treatment1Container, treatment2Container, treatment3Container],
controlVariationEntry: controlContainer,
treatmentVariationEntries: [treatment1Container, treatment2Container, treatment3Container],
};
getStringAssignmentSpy = jest.spyOn(client, 'getStringAssignment');
loggerWarnSpy = jest.spyOn(applicationLogger.logger, 'warn');
Expand All @@ -49,35 +49,35 @@ describe('getExperimentContainer', () => {

it('should return the right container when a treatment variation is assigned', async () => {
jest.spyOn(client, 'getStringAssignment').mockReturnValue('treatment-2');
expect(client.getExperimentContainer(flagExperiment, 'subject-key', {})).toEqual(
expect(client.getExperimentContainerEntry(flagExperiment, 'subject-key', {})).toEqual(
treatment2Container,
);

jest.spyOn(client, 'getStringAssignment').mockReturnValue('treatment-3');
expect(client.getExperimentContainer(flagExperiment, 'subject-key', {})).toEqual(
expect(client.getExperimentContainerEntry(flagExperiment, 'subject-key', {})).toEqual(
treatment3Container,
);
});

it('should return the right container when control is assigned', async () => {
jest.spyOn(client, 'getStringAssignment').mockReturnValue('control');
expect(client.getExperimentContainer(flagExperiment, 'subject-key', {})).toEqual(
expect(client.getExperimentContainerEntry(flagExperiment, 'subject-key', {})).toEqual(
controlContainer,
);
expect(loggerWarnSpy).not.toHaveBeenCalled();
});

it('should default to the control container if an unknown variation is assigned', async () => {
jest.spyOn(client, 'getStringAssignment').mockReturnValue('adsfsadfsadf');
expect(client.getExperimentContainer(flagExperiment, 'subject-key', {})).toEqual(
expect(client.getExperimentContainerEntry(flagExperiment, 'subject-key', {})).toEqual(
controlContainer,
);
expect(loggerWarnSpy).toHaveBeenCalled();
});

it('should default to the control container if an out-of-bounds treatment variation is assigned', async () => {
jest.spyOn(client, 'getStringAssignment').mockReturnValue('treatment-9');
expect(client.getExperimentContainer(flagExperiment, 'subject-key', {})).toEqual(
expect(client.getExperimentContainerEntry(flagExperiment, 'subject-key', {})).toEqual(
controlContainer,
);
expect(loggerWarnSpy).toHaveBeenCalled();
Expand Down
43 changes: 30 additions & 13 deletions src/client/eppo-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ export type FlagConfigurationRequestParameters = {
skipInitialPoll?: boolean;
};

export interface IFlagExperiment<T> {
export interface IContainerExperiment<T> {
flagKey: string;
controlVariation: T;
treatmentVariations: Array<T>;
controlVariationEntry: T;
treatmentVariationEntries: Array<T>;
}

export default class EppoClient {
Expand Down Expand Up @@ -531,32 +531,49 @@ export default class EppoClient {
}

/**
* For use with 3rd party CMS tooling, such as the Contentful Eppo plugin
* For use with 3rd party CMS tooling, such as the Contentful Eppo plugin.
*
* CMS plugins that integrate with Eppo will follow a common format for
* creating a feature flag. The flag created by the CMS plugin will have
* variations with values 'control', 'treatment-1', 'treatment-2', etc.
* This function allows users to easily return the CMS container entry
* for the assigned variation.
*
* @param flagExperiment the flag key, control container entry and treatment container entries.
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @returns The container entry associated with the experiment.
*/
public getExperimentContainer<T>(
flagExperiment: IFlagExperiment<T>,
public getExperimentContainerEntry<T>(
flagExperiment: IContainerExperiment<T>,
subjectKey: string,
subjectAttributes: Attributes,
): T {
const { flagKey, controlVariation, treatmentVariations } = flagExperiment;
const { flagKey, controlVariationEntry, treatmentVariationEntries } = flagExperiment;
const assignment = this.getStringAssignment(flagKey, subjectKey, subjectAttributes, 'control');
if (assignment === 'control') {
return controlVariation;
return controlVariationEntry;
}
if (!assignment.startsWith('treatment-')) {
logger.warn(
`Variation ${assignment} cannot be mapped to a container. Defaulting to control variation.`,
);
return controlVariation;
return controlVariationEntry;
}
const treatmentVariationIndex = Number.parseInt(assignment.split('-')[1]) - 1;
if (isNaN(treatmentVariationIndex)) {
logger.warn(
`Variation ${assignment} cannot be mapped to a container. Defaulting to control variation.`,
);
return controlVariationEntry;
}
const treatmentVariationIndex = Number.parseInt(assignment.split('-')[1]);
if (treatmentVariationIndex > treatmentVariations.length) {
if (treatmentVariationIndex >= treatmentVariationEntries.length) {
logger.warn(
`Selected treatment variation (${treatmentVariationIndex}) index is out of bounds. Defaulting to control variation.`,
);
return controlVariation;
return controlVariationEntry;
}
return treatmentVariations[treatmentVariationIndex - 1];
return treatmentVariationEntries[treatmentVariationIndex];
}

private evaluateBanditAction(
Expand Down
2 changes: 1 addition & 1 deletion src/client/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function initConfiguration(
queryParams: {
apiKey: 'dummy',
sdkName: 'js-client-sdk-common',
sdkVersion: '1.0.0',
sdkVersion: '3.0.0',
},
});
const httpClient = new FetchHttpClient(apiEndpoints, 1000);
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import EppoClient, {
FlagConfigurationRequestParameters,
IAssignmentDetails,
IContainerExperiment,
} from './client/eppo-client';
import FlagConfigRequestor from './configuration-requestor';
import {
Expand Down Expand Up @@ -48,6 +49,7 @@ export {
IAssignmentEvent,
IBanditLogger,
IBanditEvent,
IContainerExperiment,
EppoClient,
constants,
ApiEndpoints,
Expand Down

0 comments on commit 5e5ea30

Please sign in to comment.