ibm-appconfiguration-js-client-sdk
Version:
IBM Cloud App Configuration JavaScript Client SDK
249 lines (248 loc) • 12.5 kB
JavaScript
"use strict";
/**
* Copyright 2022 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var Constants = __importStar(require("../utils/constants"));
var hashing_1 = require("../utils/hashing");
var metering_1 = __importDefault(require("../utils/metering"));
var Cache_1 = require("./Cache");
var FeatureSegmentRule_1 = __importDefault(require("./FeatureSegmentRule"));
var logger_1 = require("../utils/logger");
var logger = new logger_1.Logger(Constants.APP_CONFIGURATION);
var Feature = /** @class */ (function () {
function Feature(_a) {
var name = _a.name, feature_id = _a.feature_id, type = _a.type, _b = _a.format, format = _b === void 0 ? undefined : _b, enabled_value = _a.enabled_value, disabled_value = _a.disabled_value, segment_rules = _a.segment_rules, enabled = _a.enabled, _c = _a.rollout_percentage, rollout_percentage = _c === void 0 ? 100 : _c;
this.name = name;
this.feature_id = feature_id;
this.type = type;
this.format = format; // will be undefined for boolean & numeric datatypes
this.enabled_value = enabled_value;
this.disabled_value = disabled_value;
this.enabled = enabled;
this.rollout_percentage = rollout_percentage;
this.segment_rules = [];
for (var _i = 0, segment_rules_1 = segment_rules; _i < segment_rules_1.length; _i++) {
var element = segment_rules_1[_i];
this.segment_rules.push(new FeatureSegmentRule_1.default(element));
}
}
/**
* Get the Feature flag name.
*
* @return {*} {string} The Feature flag name.
* @memberof Feature
*/
Feature.prototype.getFeatureName = function () {
return this.name;
};
/**
* Get the Feature flag Id.
*
* @return {*} {string} The Feature flag Id.
* @memberof Feature
*/
Feature.prototype.getFeatureId = function () {
return this.feature_id;
};
/**
* Get the Feature flag data type.
*
* @return {*} {string} string named BOOLEAN/STRING/NUMERIC.
* @memberof Feature
*/
Feature.prototype.getFeatureDataType = function () {
return this.type;
};
/**
* Get the Feature flag data format.
* Applicable only for STRING datatype feature flag.
*
* @return {*} {(string | undefined)} string named TEXT/JSON/YAML.
* @memberof Feature
*/
Feature.prototype.getFeatureDataFormat = function () {
// Format will be `undefined` for Boolean & Numeric feature flags
// If the Format is null or undefined for a String type, we default it to TEXT
if (!this.format && this.type === 'STRING') {
this.format = 'TEXT';
}
return this.format;
};
/**
* Returns the state of the feature flag.
*
* @return {*} {boolean} Returns true, if the feature flag is enabled, otherwise returns false.
* @memberof Feature
*/
Feature.prototype.isEnabled = function () {
return this.enabled;
};
/**
* Get the evaluated value of the feature flag.
*
* @param {string} entityId - Id of the Entity.
* This will be a string identifier related to the Entity against which the feature is evaluated.
* For example, an entity might be an instance of an app that runs on a mobile device, a microservice that runs on the cloud, or a component of infrastructure that runs that microservice.
* For any entity to interact with App Configuration, it must provide a unique entity ID.
*
* @param {{ [x: string]: any; }} entityAttributes - A JSON object consisting of the attribute name and their values that defines the specified entity.
* This is an optional parameter if the feature flag is not configured with any targeting definition. If the targeting is configured, then entityAttributes should be provided for the rule evaluation.
* An attribute is a parameter that is used to define a segment. The SDK uses the attribute values to determine if the
* specified entity satisfies the targeting rules, and returns the appropriate feature flag value.
*
* @return {*} {*} Returns one of the Enabled/Disabled/Overridden value based on the evaluation.
* The data type of returned value matches that of feature flag.
* @memberof Feature
*/
Feature.prototype.getCurrentValue = function (entityId, entityAttributes) {
if (entityAttributes === void 0) { entityAttributes = {}; }
if (!entityId) {
logger.log(''.concat('Feature flag evaluation: ', Constants.INVALID_ENTITY_ID, ' getCurrentValue'));
return null;
}
return this.featureEvaluation(entityId, entityAttributes).current_value;
};
Feature.prototype.featureEvaluation = function (entityId, entityAttributes) {
var evaluationResult = {
evaluated_segment_id: Constants.DEFAULT_SEGMENT_ID,
value: undefined,
is_enabled: false
};
try {
// evaluate the feature flag only if the toggle state is enabled
if (this.enabled) {
// check whether the feature flag is configured with any targeting definition and then check whether the user has passed an valid entityAttributes JSON before we evaluate
if (this.segment_rules.length > 0 && Object.keys(entityAttributes).length > 0) {
var rulesMap = this.parseRules(this.segment_rules);
evaluationResult = this.evaluateRules(rulesMap, entityId, entityAttributes);
return { current_value: evaluationResult.value, is_enabled: evaluationResult.is_enabled };
}
// since the feature flag is not configured with any targeting, use the entityId and check whether the entityId is eligible for the default rollout
if (this.rollout_percentage === 100 || ((0, hashing_1.getNormalizedValue)(''.concat(entityId, ':', this.feature_id)) < this.rollout_percentage)) {
return { current_value: this.enabled_value, is_enabled: true };
}
return { current_value: this.disabled_value, is_enabled: false };
}
return { current_value: this.disabled_value, is_enabled: false };
}
finally {
metering_1.default.getInstance().addMetering(entityId, evaluationResult.evaluated_segment_id, this.feature_id, null);
}
};
Feature.prototype.parseRules = function (segmentRules) {
var rulesMap = {};
segmentRules.forEach(function (rules) {
rulesMap[rules.order] = rules;
});
return rulesMap;
};
Feature.prototype.evaluateRules = function (rulesMap, entityId, entityAttributes) {
var resultDict = {
evaluated_segment_id: Constants.DEFAULT_SEGMENT_ID,
value: undefined,
is_enabled: false
};
try {
// for each rules in the targeting
for (var index = 1; index <= Object.keys(rulesMap).length; index += 1) {
var segmentRule = rulesMap[index];
if (segmentRule.rules.length > 0) {
for (var _i = 0, _a = segmentRule.rules; _i < _a.length; _i++) {
var level = _a[_i];
var segments = level.segments;
if (segments.length > 0) {
// for each segment in a rule
for (var _b = 0, segments_1 = segments; _b < segments_1.length; _b++) {
var innerLevel = segments_1[_b];
var segmentId = innerLevel;
// check whether the entityAttributes satifies all the rules of that segment
if (this.evaluateSegment(segmentId, entityAttributes)) {
resultDict.evaluated_segment_id = segmentId;
var segmentLevelRolloutPercentage = segmentRule.rollout_percentage === Constants.DEFAULT_ROLLOUT_PERCENTAGE ? this.rollout_percentage : segmentRule.rollout_percentage;
// check whether the entityId is eligible for segment rollout
if (segmentLevelRolloutPercentage === 100 || ((0, hashing_1.getNormalizedValue)(''.concat(entityId, ':', this.feature_id)) < segmentLevelRolloutPercentage)) {
// since the entityId is eligible for segment rollout the return value should be either of inherited or overridden value
if (segmentRule.value === Constants.DEFAULT_FEATURE_VALUE) {
resultDict.value = this.enabled_value; // return the inherited value
}
else {
resultDict.value = segmentRule.value; // return the overridden value
}
resultDict.is_enabled = true;
}
else {
resultDict.value = this.disabled_value;
resultDict.is_enabled = false;
}
return resultDict;
}
}
}
}
}
}
}
catch (e) {
console.error(''.concat(Constants.APP_CONFIGURATION, 'FeatureFlagRuleEvaluation', e.message));
}
// since entityAttributes did not satisfy any of the targeting rules
// check whether the entityId is eligible for default rollout
if (this.rollout_percentage === 100 || ((0, hashing_1.getNormalizedValue)(''.concat(entityId, ':', this.feature_id)) < this.rollout_percentage)) {
resultDict.value = this.enabled_value;
resultDict.is_enabled = true;
}
else {
resultDict.value = this.disabled_value;
resultDict.is_enabled = false;
}
return resultDict;
};
Feature.prototype.evaluateSegment = function (segmentId, entityAttributes) {
var segment = (0, Cache_1.getCacheInstance)().segments;
if (Object.prototype.hasOwnProperty.call(segment, segmentId)) {
return segment[segmentId].evaluateRule(entityAttributes);
}
return false;
};
return Feature;
}());
exports.default = Feature;