8000 feat: datafile accessor (#535) · rserrano-eSW/javascript-sdk@03b573e · GitHub
[go: up one dir, main page]

Skip to content

Commit 03b573e

Browse files
pthompson127Matt Carrollmsohailhussainmnoman09
authored
feat: datafile accessor (optimizely#535)
Summary: Add a getDatafile method to the OptimizelyConfig object, that returns a string version of the datafile currently being used by the instance. - toDatafile function added in project_config module to encapsulate conversion of project config to datafile string - Parsing & validation code updated (in both optimizely-sdk and updated datafile-manager version) to only call JSON.parse at most once. Test Plan: Manual testing Passing new FSC datafile accessor tests Updated unit tests Co-authored-by: Matt Carroll <matt.carroll@optimizely.com> Co-authored-by: Sohail Hussain <mirza.sohailhussain@gmail.com> Co-authored-by: muhammadnoman <muhammadnoman@folio3.com>
1 parent d32c8b0 commit 03b573e

File tree

14 files changed

+247
-219
lines changed

14 files changed

+247
-219
lines changed

packages/optimizely-sdk/.eslintrc.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ module.exports = {
1414
Promise: 'readonly',
1515
},
1616
parserOptions: {
17-
ecmaVersion: 6,
17+
// Note: The TS compiler determines what syntax is accepted. We're using TS version 3.3.3333.
18+
// This seems to roughly correspond to "2018" for this setting.
19+
ecmaVersion: 2018,
1820
sourceType: 'module',
1921
},
2022
overrides: [

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,26 @@ function getFeaturesMap(configObj, allExperiments) {
110110
}, {});
111111
}
112112

113-
export var getOptimizelyConfig = function(configObj) {
114-
// Fetch all feature variables from feature flags to merge them with variation variables
115-
var experimentsMap = getExperimentsMap(configObj);
116-
return {
117-
experimentsMap: experimentsMap,
118-
featuresMap: getFeaturesMap(configObj, experimentsMap),
119-
revision: configObj.revision,
120-
};
121-
};
113+
/**
114+
* The OptimizelyConfig class
115+
* @param {Object} configObj
116+
* @param {string} datafile
117+
*/
118+
export function OptimizelyConfig(configObj, datafile) {
119+
this.experimentsMap = getExperimentsMap(configObj);
120+
this.featuresMap = getFeaturesMap(configObj, this.experimentsMap);
121+
this.revision = configObj.revision;
122+
this.__datafile = datafile;
123+
}
124+
125+
/**
126+
* Get the datafile
127+
* @returns {string} JSON string representation of the datafile that was used to create the current config object
128+
*/
129+
OptimizelyConfig.prototype.getDatafile = function() {
130+
return this.__datafile;
131+
}
132+
133+
export default {
134+
OptimizelyConfig: OptimizelyConfig
135+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import { assert } from 'chai';
1717
import { cloneDeep } from 'lodash';
1818

19-
import { getOptimizelyConfig } from './index';
19+
import { OptimizelyConfig } from './index';
2020
import { createProjectConfig } from '../project_config';
2121
import { getTestProjectConfigWithFeatures } from '../../tests/test_data';
2222

@@ -41,7 +41,7 @@ describe('lib/core/optimizely_config', function() {
4141
var projectConfigObject;
4242
beforeEach(function() {
4343
projectConfigObject = createProjectConfig(cloneDeep(datafile));
44-
optimizelyConfigObject = getOptimizelyConfig(projectConfigObject);
44+
optimizelyConfigObject = new OptimizelyConfig(projectConfigObject, JSON.stringify(datafile));
4545
});
4646

4747
it('should return all experiments except rollouts', function() {

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

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import { sprintf, objectValues } from '@optimizely/js-sdk-utils';
1818
import { assign, keyBy } from '../../utils/fns';
1919
import {
2020
ERROR_MESSAGES,
21-
LOG_MESSAGES,
2221
LOG_LEVEL,
22+
LOG_MESSAGES,
2323
FEATURE_VARIABLE_TYPES,
2424
} from '../../utils/enums';
2525
import configValidator from '../../utils/config_validator';
@@ -58,11 +58,14 @@ function createMutationSafeDatafileCopy(datafile) {
5858

5959
/**
6060
* Creates projectConfig object to be used for quick project property lookup
61-
* @param {Object} datafile JSON datafile representing the project
62-
* @return {Object} Object representing project configuration
61+
* @param {Object} datafileObj JSON datafile representing the project
62+
* @param {string=} datafileStr JSON string representation of the datafile
63+
* @return {Object} Object representing project configuration
6364
*/
64-
export var createProjectConfig = function(datafile) {
65-
var projectConfig = createMutationSafeDatafileCopy(datafile);
65+
export var createProjectConfig = function(datafileObj, datafileStr=null) {
66+
var projectConfig = createMutationSafeDatafileCopy(datafileObj);
67+
68+
projectConfig.__datafileStr = datafileStr === null ? JSON.stringify(datafileObj) : datafileStr;
6669

6770
/*
6871
* Conditions of audiences in projectConfig.typedAudiences are not
@@ -512,7 +515,7 @@ export var getTypeCastValue = function(variableValue, variableType, logger) {
512515
/**
513516
* Returns an object containing all audiences in the project config. Keys are audience IDs
514517
* and values are audience objects.
515-
* @param projectConfig
518+
* @param {Object} projectConfig
516519
* @returns {Object}
517520
*/
518521
export var getAudiencesById = function(projectConfig) {
@@ -521,44 +524,83 @@ export var getAudiencesById = function(projectConfig) {
521524

522525
/**
523526
* Returns true if an event with the given key exists in the datafile, and false otherwise
524-
* @param {Object} projectConfig
525-
* @param {string} eventKey
527+
* @param {Object} projectConfig
528+
* @param {string} eventKey
526529
* @returns {boolean}
527530
*/
528531
export var eventWithKeyExists = function(projectConfig, eventKey) {
529532
return projectConfig.eventKeyMap.hasOwnProperty(eventKey);
530533
};
531534

532535
/**
533-
*
536+
* Returns true if experiment belongs to any feature, false otherwise.
534537
* @param {Object} projectConfig
535538
* @param {string} experimentId
536-
* @returns {boolean} Returns true if experiment belongs to
537-
* any feature, false otherwise.
539+
* @returns {boolean}
538540
*/
539541
export var isFeatureExperiment = function(projectConfig, experimentId) {
540542
return projectConfig.experimentFeatureMap.hasOwnProperty(experimentId);
541543
};
542544

545+
/**
546+
* Returns the JSON string representation of the datafile
547+
* @param {Object} projectConfig
548+
* @returns {string}
549+
*/
550+
export var toDatafile = function(projectConfig) {
551+
return projectConfig.__datafileStr;
552+
}
553+
554+
/**
555+
* @typedef {Object} TryCreatingProjectConfigResult
556+
* @property {Object|null} configObj
557+
* @property {Error|null} error
558+
*/
559+
543560
/**
544561
* Try to create a project config object from the given datafile and
545562
* configuration properties.
546-
* If successful, return the project config object, otherwise throws an error
547-< 10000 div class="diff-text-inner"> * @param {Object} config
548-
* @param {Object} config.datafile
549-
* @param {Object} config.jsonSchemaValidator
550-
* @param {Object} config.logger
551-
* @return {Object} Project config object
563+
* Returns an object with configObj and error properties.
564+
* If successful, configObj is the project config object, and error is null.
565+
* Otherwise, configObj is null and error is an error with more information.
566+
* @param {Object} config
567+
* @param {Object|string} config.datafile
568+
* @param {Object} config.jsonSchemaValidator
569+
* @param {Object} config.logger
570+
* @returns {TryCreatingProjectConfigResult}
552571
*/
553572
export var tryCreatingProjectConfig = function(config) {
554-
configValidator.validateDatafile(config.datafile);
555-
if (!config.jsonSchemaValidator) {
556-
config.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.SKIPPING_JSON_VALIDATION, MODULE_NAME));
573+
574+
var newDatafileObj;
575+
try {
576+
newDatafileObj = configValidator.validateDatafile(config.datafile);
577+
} catch (error) {
578+
return { configObj: null, error };
579+
}
580+
581+
if (config.jsonSchemaValidator) {
582+
try {
583+
config.jsonSchemaValidator.validate(newDatafileObj);
584+
config.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.VALID_DATAFILE, MODULE_NAME));
585+
} catch (error) {
586+
return { configObj: null, error };
587+
}
557588
} else {
558-
config.jsonSchemaValidator.validate(config.datafile);
559-
config.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.VALID_DATAFILE, MODULE_NAME));
589+
config.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.SKIPPING_JSON_VALIDATION, MODULE_NAME));
590+
}
591+
592+
var createProjectConfigArgs = [newDatafileObj];
593+
if (typeof config.datafile === 'string') {
594+
// Since config.datafile was validated above, we know that it is a valid JSON string
595+
createProjectConfigArgs.push(config.datafile);
560596
}
561-
return this.createProjectConfig(config.datafile);
597+
598+
var newConfigObj = createProjectConfig(...createProjectConfigArgs);
599+
600+
return {
601+
configObj: newConfigObj,
602+
error: null,
603+
};
562604
};
563605

564606
export default {
@@ -583,5 +625,6 @@ export default {
583625
getAudiencesById: getAudiencesById,
584626
eventWithKeyExists: eventWithKeyExists,
585627
isFeatureExperiment: isFeatureExperiment,
628+
toDatafile: toDatafile,
586629
tryCreatingProjectConfig: tryCreatingProjectConfig,
587630
};

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

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import sinon from 'sinon';
17-
import { assert } from 'chai';
17+
import { assert, expect, config } from 'chai';
1818
import { forEach, cloneDeep } from 'lodash';
1919
import { getLogger } from '@optimizely/js-sdk-logging';
2020
import { sprintf } from '@optimizely/js-sdk-utils';
@@ -712,75 +712,91 @@ describe('lib/core/project_config', function() {
712712
stubJsonSchemaValidator = {
713713
validate: sinon.stub().returns(true),
714714
};
715-
sinon.stub(projectConfig, 'createProjectConfig').returns({});
716715
sinon.stub(configValidator, 'validateDatafile').returns(true);
717716
sinon.spy(logger, 'error');
718717
});
719718

720719
afterEach(function() {
721-
projectConfig.createProjectConfig.restore();
722720
configValidator.validateDatafile.restore();
723721
logger.error.restore();
724722
});
725723

726724
it('returns a project config object created by createProjectConfig when all validation is applied and there are no errors', function() {
727-
configValidator.validateDatafile.returns(true);
728-
stubJsonSchemaValidator.validate.returns(true);
725+
var configDatafile = {
726+
foo: 'bar',
727+
experiments: [
728+
{key: 'a'},
729+
{key: 'b'}
730+
]
731+
}
732+
configValidator.validateDatafile.returns(configDatafile);
729733
var configObj = {
730734
foo: 'bar',
731735
experimentKeyMap: {
732-
a: { key: 'a' },
733-
b: { key: 'b' },
736+
"a": { key: "a", variationKeyMap: {} },
737+
"b": { key: "b", variationKeyMap: {} }
734738
},
735739
};
736-
projectConfig.createProjectConfig.returns(configObj);
740+
741+
stubJsonSchemaValidator.validate.returns(true);
742+
737743
var result = projectConfig.tryCreatingProjectConfig({
738-
datafile: { foo: 'bar' },
744+
datafile: configDatafile,
739745
jsonSchemaValidator: stubJsonSchemaValidator,
740746
logger: logger,
741747
});
742-
assert.deepEqual(result, configObj);
748+
749+
assert.deepInclude(result.configObj, configObj)
743750
});
744751

745-
it('throws an error when validateDatafile throws', function() {
752+
it('returns an error when validateDatafile throws', function() {
746753
configValidator.validateDatafile.throws();
747754
stubJsonSchemaValidator.validate.returns(true);
748-
assert.throws(function() {
749-
projectConfig.tryCreatingProjectConfig({
750-
datafile: { foo: 'bar' },
751-
jsonSchemaValidator: stubJsonSchemaValidator,
752-
logger: logger,
753-
});
755+
var { error } = projectConfig.tryCreatingProjectConfig({
756+
datafile: { foo: 'bar' },
757+
jsonSchemaValidator: stubJsonSchemaValidator,
758+
logger: logger,
754759
});
760+
assert.isNotNull(error);
755761
});
756762

757-
it('throws an error when jsonSchemaValidator.validate throws', function() {
763+
it('returns an error when jsonSchemaValidator.validate throws', function() {
758764
configValidator.validateDatafile.returns(true);
759765
stubJsonSchemaValidator.validate.throws();
760-
assert.throws(function() {
761-
var result = projectConfig.tryCreatingProjectConfig({
762-
datafile: { foo: 'bar' },
763-
jsonSchemaValidator: stubJsonSchemaValidator,
764-
logger: logger,
765-
});
766+
var { error } = projectConfig.tryCreatingProjectConfig({
767+
datafile: { foo: 'bar' },
768+
jsonSchemaValidator: stubJsonSchemaValidator,
769+
logger: logger,
766770
});
771+
assert.isNotNull(error);
767772
});
768773

769774
it('skips json validation when jsonSchemaValidator is not provided', function() {
770-
configValidator.validateDatafile.returns(true);
775+
776+
var configDatafile = {
777+
foo: 'bar',
778+
experiments: [
779+
{key: 'a'},
780+
{key: 'b'}
781+
]
782+
}
783+
784+
configValidator.validateDatafile.returns(configDatafile);
785+
771786
var configObj = {
772787
foo: 'bar',
773788
experimentKeyMap: {
774-
a: { key: 'a' },
775-
b: { key: 'b' },
789+
a: { key: 'a', variationKeyMap: {} },
790+
b: { key: 'b', variationKeyMap: {} },
776791
},
777792
};
778-
projectConfig.createProjectConfig.returns(configObj);
793+
779794
var result = projectConfig.tryCreatingProjectConfig({
780-
datafile: { foo: 'bar' },
795+
datafile: configDatafile,
781796
logger: logger,
782797
});
783-
assert.deepEqual(result, configObj);
798+
799+
assert.deepInclude(result.configObj, configObj);
784800
sinon.assert.notCalled(logger.error);
785801
});
786802
});

0 commit comments

Comments
 (0)
0