Skip to content

Commit ded5a92

Browse files
committed
init
1 parent a4dcc3e commit ded5a92

File tree

4 files changed

+242
-64
lines changed

4 files changed

+242
-64
lines changed

packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,13 @@ describe('lib/core/optimizely_config', function() {
145145
it('should return correct config environmentKey ', function() {
146146
assert.equal(optimizelyConfigObject.environmentKey, datafile.environmentKey);
147147
});
148+
149+
it('should return correct config attributes', function() {
150+
assert.deepEqual(datafile.attributes, optimizelyConfigObject.attributes);
151+
});
152+
153+
it('should return correct config events', function() {
154+
assert.deepEqual(datafile.events, optimizelyConfigObject.events);
155+
});
148156
});
149157
});

packages/optimizely-sdk/lib/core/optimizely_config/index.ts

Lines changed: 207 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import {
2222
VariationVariable,
2323
Variation,
2424
Rollout,
25+
OptimizelyAttribute,
26+
OptimizelyAudience,
27+
OptimizelyEvents
2528
} from '../../shared_types';
2629

2730
interface FeatureVariablesMap {
@@ -38,15 +41,51 @@ export class OptimizelyConfig {
3841
public featuresMap: OptimizelyFeaturesMap;
3942
public revision: string;
4043
public sdkKey?: string;
44+
public attributes: OptimizelyAttribute[];
45+
public audiences: OptimizelyAudience[];
46+
public events: OptimizelyEvents[];
4147
public environmentKey?: string;
4248
private datafile: string;
4349

4450
constructor(configObj: ProjectConfig, datafile: string) {
45-
this.experimentsMap = OptimizelyConfig.getExperimentsMap(configObj);
46-
this.featuresMap = OptimizelyConfig.getFeaturesMap(configObj, this.experimentsMap);
4751
this.revision = configObj.revision;
52+
this.attributes = configObj.attributes;
53+
this.audiences = [];
54+
this.events = configObj.events;
4855
this.datafile = datafile;
56+
const audiences = configObj.typedAudiences || [];
4957

58+
configObj.audiences.forEach((oldAudience) => {
59+
if (
60+
audiences.filter((newAudience) => {
61+
newAudience == oldAudience;
62+
}).length == 0
63+
) {
64+
if (oldAudience.id != '$opt_dummy_audience') {
65+
audiences.push(oldAudience);
66+
}
67+
}
68+
});
69+
70+
this.audiences = audiences;
71+
const audienceMap: { [key: string]: string } = {};
72+
73+
for (const audience of this.audiences) {
74+
audienceMap[audience.id] = audience.name;
75+
}
76+
const updatedExperiments = OptimizelyConfig.getExperimentsMap(configObj);
77+
78+
Object.keys(updatedExperiments).map(function (key) {
79+
const audiencesSerialized = serializeAudiences(configObj, key, audienceMap);
80+
if (audiencesSerialized) {
81+
updatedExperiments[key].audiences = audiencesSerialized;
82+
}
83+
});
84+
const updatedRollouts = OptimizelyConfig.updateRollouts(configObj, audienceMap);
85+
86+
this.experimentsMap = updatedExperiments;
87+
this.featuresMap = OptimizelyConfig.getFeaturesMap(configObj, updatedExperiments, updatedRollouts);
88+
console.log("FEATURES MAP:", this.featuresMap)
5089
if (configObj.sdkKey && configObj.environmentKey) {
5190
this.sdkKey = configObj.sdkKey;
5291
this.environmentKey = configObj.environmentKey;
@@ -69,57 +108,67 @@ export class OptimizelyConfig {
69108
static getRolloutExperimentIds(rollouts: Rollout[]): { [key: string]: boolean } {
70109
return (rollouts || []).reduce((experimentIds: { [key: string]: boolean }, rollout) => {
71110
rollout.experiments.forEach((e) => {
72-
(experimentIds)[e.id] = true;
111+
experimentIds[e.id] = true;
73112
});
74113

75114
return experimentIds;
76115
}, {});
77116
}
78117

118+
/**
119+
* Update rollouts by adding audiences keys in experiments
120+
* @param {ProjectConfig} configObj
121+
* @returns {audienceMap} Map of audiences
122+
*/
123+
static updateRollouts(configObj: ProjectConfig, audienceMap: { [key: string]: string }): Rollout[] {
124+
return configObj.rollouts.map((rollout) => {
125+
rollout.experiments = rollout.experiments.map((experiment) => {
126+
const audiences = serializeAudiences(configObj, experiment.key, audienceMap);
127+
if (audiences) {
128+
experiment.audiences = audiences;
129+
}
130+
return experiment;
131+
});
132+
return rollout;
133+
});
134+
}
135+
79136
/**
80137
* Get Map of all experiments except rollouts
81138
* @param {ProjectConfig} configObj
82139
* @returns {OptimizelyExperimentsMap} Map of experiments excluding rollouts
83140
*/
84141
static getExperimentsMap(configObj: ProjectConfig): OptimizelyExperimentsMap {
85142
const rolloutExperimentIds = this.getRolloutExperimentIds(configObj.rollouts);
86-
const featureVariablesMap = (configObj.featureFlags || []).reduce(
87-
(resultMap: FeatureVariablesMap, feature) => {
88-
resultMap[feature.id] = feature.variables;
89-
return resultMap;
90-
},
91-
{},
92-
);
93-
94-
return (configObj.experiments || []).reduce(
95-
(experiments: OptimizelyExperimentsMap, experiment) => {
96-
// skip experiments that are part of a rollout
97-
if (!rolloutExperimentIds[experiment.id]) {
98-
experiments[experiment.key] = {
99-
id: experiment.id,
100-
key: experiment.key,
101-
variationsMap: (experiment.variations || []).reduce(
102-
(variations: { [key: string]: Variation }, variation) => {
103-
variations[variation.key] = {
104-
id: variation.id,
105-
key: variation.key,
106-
variablesMap: this.getMergedVariablesMap(configObj, variation, experiment.id, featureVariablesMap),
107-
};
108-
if (isFeatureExperiment(configObj, experiment.id)) {
109-
variations[variation.key].featureEnabled = variation.featureEnabled;
110-
}
111-
112-
return variations;
113-
},
114-
{},
115-
),
116-
};
117-
}
143+
const featureVariablesMap = (configObj.featureFlags || []).reduce((resultMap: FeatureVariablesMap, feature) => {
144+
resultMap[feature.id] = feature.variables;
145+
return resultMap;
146+
}, {});
147+
148+
return (configObj.experiments || []).reduce((experiments: OptimizelyExperimentsMap, experiment) => {
149+
// skip experiments that are part of a rollout
150+
if (!rolloutExperimentIds[experiment.id]) {
151+
experiments[experiment.key] = {
152+
id: experiment.id,
153+
key: experiment.key,
154+
variationsMap: (experiment.variations || []).reduce((variations: { [key: string]: Variation }, variation) => {
155+
variations[variation.key] = {
156+
id: variation.id,
157+
key: variation.key,
158+
variablesMap: this.getMergedVariablesMap(configObj, variation, experiment.id, featureVariablesMap),
159+
};
160+
if (isFeatureExperiment(configObj, experiment.id)) {
161+
variations[variation.key].featureEnabled = variation.featureEnabled;
162+
}
118163

119-
return experiments;
120-
},
121-
{},
122-
)
164+
return variations;
165+
}, {}),
166+
audiences: experiment.audiences,
167+
};
168+
}
169+
170+
return experiments;
171+
}, {});
123172
}
124173

125174
/**
@@ -134,7 +183,7 @@ export class OptimizelyConfig {
134183
configObj: ProjectConfig,
135184
variation: Variation,
136185
experimentId: string,
137-
featureVariablesMap: FeatureVariablesMap,
186+
featureVariablesMap: FeatureVariablesMap
138187
): OptimizelyVariablesMap {
139188
const featureId = configObj.experimentFeatureMap[experimentId];
140189

@@ -151,7 +200,7 @@ export class OptimizelyConfig {
151200

152201
return variablesMap;
153202
},
154-
{},
203+
{}
155204
);
156205
variablesObject = (experimentFeatureVariables || []).reduce(
157206
(variablesMap: OptimizelyVariablesMap, featureVariable) => {
@@ -167,7 +216,7 @@ export class OptimizelyConfig {
167216

168217
return variablesMap;
169218
},
170-
{},
219+
{}
171220
);
172221
}
173222

@@ -182,40 +231,135 @@ export class OptimizelyConfig {
182231
*/
183232
static getFeaturesMap(
184233
configObj: ProjectConfig,
185-
allExperiments: OptimizelyExperimentsMap
234+
allExperiments: OptimizelyExperimentsMap,
235+
rollouts: Rollout[]
186236
): OptimizelyFeaturesMap {
187237
return (configObj.featureFlags || []).reduce((features: OptimizelyFeaturesMap, feature) => {
238+
const filteredRollout = rollouts.filter((rollout) => {
239+
console.log("Rollout:", rollout.id, "Feature:", feature.id)
240+
return rollout.id == feature.id;
241+
});
242+
console.log("FILTERED ROLLOUTS ", filteredRollout)
188243
features[feature.key] = {
189244
id: feature.id,
190245
key: feature.key,
191-
experimentsMap: (feature.experimentIds || []).reduce(
192-
(experiments: OptimizelyExperimentsMap, experimentId) => {
193-
const experimentKey = configObj.experimentIdMap[experimentId].key;
194-
experiments[experimentKey] = allExperiments[experimentKey];
195-
return experiments;
196-
},
197-
{},
198-
),
199-
variablesMap: (feature.variables || []).reduce(
200-
(variables: OptimizelyVariablesMap, variable) => {
201-
variables[variable.key] = {
202-
id: variable.id,
203-
key: variable.key,
204-
type: variable.type,
205-
value: variable.defaultValue,
206-
};
246+
experimentsMap: (feature.experimentIds || []).reduce((experiments: OptimizelyExperimentsMap, experimentId) => {
247+
const experimentKey = configObj.experimentIdMap[experimentId].key;
248+
experiments[experimentKey] = allExperiments[experimentKey];
249+
return experiments;
250+
}, {}),
251+
variablesMap: (feature.variables || []).reduce((variables: OptimizelyVariablesMap, variable) => {
252+
variables[variable.key] = {
253+
id: variable.id,
254+
key: variable.key,
255+
type: variable.type,
256+
value: variable.defaultValue,
257+
};
207258

208-
return variables;
209-
},
210-
{},
211-
),
259+
return variables;
260+
}, {}),
261+
deliveryRules: Object.values(allExperiments),
262+
experimentRules: filteredRollout
263+
? filteredRollout[0].experiments.map((experiment) => {
264+
return {
265+
id: experiment.id,
266+
key: experiment.key,
267+
audiences: experiment.audiences,
268+
variationsMap: experiment.variationKeyMap,
269+
};
270+
})
271+
: [],
212272
};
213273

214274
return features;
215275
}, {});
216276
}
217277
}
218278

279+
/**
280+
* Serialize audienceConditions
281+
* @param {Array<string | string[]>} condition
282+
* @returns {string} serialized audience condition
283+
*/
284+
function serialized(condition: Array<string | string[]>) {
285+
const operator = condition[0];
286+
let first = '';
287+
let second = '';
288+
if (condition[1]) {
289+
first = Array.isArray(condition[1]) ? `(${serialized(condition[1])})` : `AUDIENCE(${condition[1]})`;
290+
}
291+
if (condition[2]) {
292+
second = Array.isArray(condition[2]) ? `(${serialized(condition[2])})` : `AUDIENCE(${condition[2]})`;
293+
}
294+
if (condition[1] && condition[2]) {
295+
return `${first} ${operator.toString().toUpperCase()} ${second}`;
296+
} else {
297+
return `${operator.toString().toUpperCase()} ${first}`;
298+
}
299+
}
300+
301+
/**
302+
* replace audience ids with name
303+
* @param {string} condition
304+
* @param {{[key: string]: string}} audiences
305+
* @returns {string} Updated serialized audienceCondition
306+
*/
307+
function replaceAudienceIdsWithNames(condition: string, audiences: {[key: string]: string}) {
308+
const beginWord = "AUDIENCE(";
309+
const endWord = ")";
310+
let keyIdx = 0;
311+
let audienceId = "";
312+
let collect = false;
313+
314+
let replaced = "";
315+
for (const ch of condition) {
316+
if (collect) {
317+
if (ch == endWord) {
318+
replaced += `"${audiences[audienceId] || audienceId}"`;
319+
collect = false;
320+
audienceId = "";
321+
}
322+
else {
323+
audienceId += ch;
324+
}
325+
continue;
326+
}
327+
328+
if (ch == beginWord[keyIdx]) {
329+
keyIdx += 1;
330+
if (keyIdx == beginWord.length) {
331+
keyIdx = 0;
332+
collect = true;
333+
}
334+
continue;
335+
}
336+
else {
337+
if (keyIdx > 0) {
338+
replaced += beginWord.substring(0, keyIdx);
339+
}
340+
keyIdx = 0;
341+
}
342+
343+
replaced += ch;
344+
}
345+
346+
return replaced;
347+
}
348+
349+
/**
350+
* Return serialized audienceCondtion with replaced audienceIds with names
351+
* @param {Array<string | string[]>} condition
352+
* @returns {string} serialized audience condition
353+
*/
354+
function serializeAudiences(configObj: ProjectConfig, experimentKey: string, audienceMap: { [key: string]: string }) {
355+
const experiment = configObj.experimentKeyMap[experimentKey];
356+
if (experiment.audienceConditions) {
357+
const condition = serialized(experiment.audienceConditions);
358+
return replaceAudienceIdsWithNames(condition, audienceMap);
359+
}
360+
return '';
361+
}
362+
219363
/**
220364
* Create an instance of OptimizelyConfig
221365
* @param {ProjectConfig} configObj

packages/optimizely-sdk/lib/core/project_config/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ interface TryCreatingProjectConfigConfig {
4949
interface Event {
5050
key: string;
5151
id: string;
52+
experimentsIds: string;
5253
}
5354

5455
interface VariableUsageMap {
@@ -80,7 +81,7 @@ export interface ProjectConfig {
8081
groupIdMap: { [id: string]: Group };
8182
groups: Group[];
8283
events: Event[];
83-
attributes: Array<{ id: string }>;
84+
attributes: Array<{ id: string, key:string, name:string }>;
8485
typedAudiences: Audience[];
8586
rolloutIdMap: { [id: string]: Rollout };
8687
anonymizeIP?: boolean | null;

0 commit comments

Comments
 (0)