UNPKG

ibm-appconfiguration-js-client-sdk

Version:
249 lines (248 loc) 12.5 kB
"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;