Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 7 additions & 44 deletions lib/project_config/project_config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,20 +393,6 @@ describe('createProjectConfig - holdouts', () => {
holdout_id_3: configObj.holdouts[2],
});

expect(configObj.globalHoldouts).toHaveLength(2);
expect(configObj.globalHoldouts).toEqual([
configObj.holdouts[0], // holdout_1 has empty includedFlags
configObj.holdouts[1] // holdout_2 has empty includedFlags
]);

expect(configObj.includedHoldouts).toEqual({
feature_1: [configObj.holdouts[2]], // holdout_3 includes feature_1 (ID: 4482920077)
});

expect(configObj.excludedHoldouts).toEqual({
feature_3: [configObj.holdouts[1]] // holdout_2 excludes feature_3 (ID: 44829230000)
});

expect(configObj.flagHoldoutsMap).toEqual({});
});

Expand All @@ -417,50 +403,27 @@ describe('createProjectConfig - holdouts', () => {

expect(configObj.holdouts).toEqual([]);
expect(configObj.holdoutIdMap).toEqual({});
expect(configObj.globalHoldouts).toEqual([]);
expect(configObj.includedHoldouts).toEqual({});
expect(configObj.excludedHoldouts).toEqual({});
expect(configObj.flagHoldoutsMap).toEqual({});
});

it('should handle undefined includedFlags and excludedFlags in holdout', function() {
const datafile = getHoldoutDatafile();
datafile.holdouts[0].includedFlags = undefined;
datafile.holdouts[0].excludedFlags = undefined;

const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile)));

expect(configObj.holdouts).toHaveLength(3);
expect(configObj.holdouts[0].includedFlags).toEqual([]);
expect(configObj.holdouts[0].excludedFlags).toEqual([]);
});
});

describe('getHoldoutsForFlag', () => {
it('should return all applicable holdouts for a flag', () => {
it('should return all holdouts for any flag', () => {
const datafile = getHoldoutDatafile();
const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile)));

// All holdouts now apply globally to all flags
const feature1Holdouts = getHoldoutsForFlag(configObj, 'feature_1');
expect(feature1Holdouts).toHaveLength(3);
expect(feature1Holdouts).toEqual([
configObj.holdouts[0],
configObj.holdouts[1],
configObj.holdouts[2],
]);
expect(feature1Holdouts).toEqual(configObj.holdouts);

const feature2Holdouts = getHoldoutsForFlag(configObj, 'feature_2');
expect(feature2Holdouts).toHaveLength(2);
expect(feature2Holdouts).toEqual([
configObj.holdouts[0],
configObj.holdouts[1],
]);
expect(feature2Holdouts).toHaveLength(3);
expect(feature2Holdouts).toEqual(configObj.holdouts);

const feature3Holdouts = getHoldoutsForFlag(configObj, 'feature_3');
expect(feature3Holdouts).toHaveLength(1);
expect(feature3Holdouts).toEqual([
configObj.holdouts[0],
]);
expect(feature3Holdouts).toHaveLength(3);
expect(feature3Holdouts).toEqual(configObj.holdouts);
});
});

Expand Down
55 changes: 3 additions & 52 deletions lib/project_config/project_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,6 @@ export interface ProjectConfig {
odpIntegrationConfig: OdpIntegrationConfig;
holdouts: Holdout[];
holdoutIdMap?: { [id: string]: Holdout };
globalHoldouts: Holdout[];
includedHoldouts: { [key: string]: Holdout[]; }
excludedHoldouts: { [key: string]: Holdout[]; }
flagHoldoutsMap: { [key: string]: Holdout[]; }
}

Expand Down Expand Up @@ -390,51 +387,11 @@ const getEveryoneElseVariation = function(
const parseHoldoutsConfig = (projectConfig: ProjectConfig): void => {
projectConfig.holdouts = projectConfig.holdouts || [];
projectConfig.holdoutIdMap = keyBy(projectConfig.holdouts, 'id');
projectConfig.globalHoldouts = [];
projectConfig.includedHoldouts = {};
projectConfig.excludedHoldouts = {};
projectConfig.flagHoldoutsMap = {};

const featureFlagIdMap = keyBy(projectConfig.featureFlags, 'id');

projectConfig.holdouts.forEach((holdout) => {
if (!holdout.includedFlags) {
holdout.includedFlags = [];
}

if (!holdout.excludedFlags) {
holdout.excludedFlags = [];
}

holdout.variationKeyMap = keyBy(holdout.variations, 'key');

assignBy(holdout.variations, 'id', projectConfig.variationIdMap);

if (holdout.includedFlags.length === 0) {
projectConfig.globalHoldouts.push(holdout);

holdout.excludedFlags.forEach((flagId: string) => {
const flag = featureFlagIdMap[flagId];
if (flag) {
const flagKey = flag.key;
if (!projectConfig.excludedHoldouts[flagKey]) {
projectConfig.excludedHoldouts[flagKey] = [];
}
projectConfig.excludedHoldouts[flagKey].push(holdout);
}
});
} else {
holdout.includedFlags.forEach((flagId: string) => {
const flag = featureFlagIdMap[flagId];
if (flag) {
const flagKey = flag.key;
if (!projectConfig.includedHoldouts[flagKey]) {
projectConfig.includedHoldouts[flagKey] = [];
}
projectConfig.includedHoldouts[flagKey].push(holdout);
}
})
}
});
}

Expand All @@ -443,15 +400,9 @@ export const getHoldoutsForFlag = (projectConfig: ProjectConfig, flagKey: string
return projectConfig.flagHoldoutsMap[flagKey];
}

const flagHoldouts: Holdout[] = [
...projectConfig.globalHoldouts.filter((holdout) => {
return !(projectConfig.excludedHoldouts[flagKey] || []).includes(holdout);
}),
...(projectConfig.includedHoldouts[flagKey] || []),
];

projectConfig.flagHoldoutsMap[flagKey] = flagHoldouts;
return flagHoldouts;
// All holdouts now apply to all flags (global holdouts)
projectConfig.flagHoldoutsMap[flagKey] = projectConfig.holdouts;
return projectConfig.holdouts;
}

/**
Expand Down
10 changes: 2 additions & 8 deletions lib/shared_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,11 @@ export type HoldoutStatus = 'Draft' | 'Running' | 'Concluded' | 'Archived';

export interface Holdout extends ExperimentCore {
status: HoldoutStatus;
includedFlags: string[];
excludedFlags: string[];
}

export function isHoldout(obj: Experiment | Holdout): obj is Holdout {
// Holdout has 'status', 'includedFlags', and 'excludedFlags' properties
return (
(obj as Holdout).status !== undefined &&
Array.isArray((obj as Holdout).includedFlags) &&
Array.isArray((obj as Holdout).excludedFlags)
);
// Holdout doesn't have 'layerId' property, while Experiment does
return (obj as Experiment).layerId === undefined;
}

export enum VariableType {
Expand Down
Loading