Skip to content

Commit ed62cf7

Browse files
[FSSDK-10766] decideForKeys adjustment
1 parent 6c7657d commit ed62cf7

File tree

2 files changed

+112
-74
lines changed

2 files changed

+112
-74
lines changed

lib/core/decision_service/index.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,13 @@ export class DecisionService {
107107
}
108108

109109
/**
110-
* Resolves variation where visitor will be bucketed.
111-
* @param {ProjectConfig} configObj The parsed project configuration object
112-
* @param {Experiment} experiment
113-
* @param {OptimizelyUserContext} user A user context
114-
* @return {DecisionResponse<string|null>} DecisionResponse containing the variation the user is bucketed into
115-
* and the decide reasons.
110+
* Resolves the variation into which the visitor will be bucketed.
111+
*
112+
* @param {ProjectConfig} configObj - The parsed project configuration object.
113+
* @param {Experiment} experiment - The experiment for which the variation is being resolved.
114+
* @param {OptimizelyUserContext} user - The user context associated with this decision.
115+
* @returns {DecisionResponse<string|null>} - A DecisionResponse containing the variation the user is bucketed into,
116+
* along with the decision reasons.
116117
*/
117118
private resolveVariation(
118119
configObj: ProjectConfig,
@@ -580,11 +581,14 @@ export class DecisionService {
580581
}
581582

582583
/**
583-
* @param {ProjectConfig} configObj The parsed project configuration object
584-
* @param {FeatureFlag[]} featureFlags Feature flags for which variations are to be determined
585-
* @param {OptimizelyUserContext} user A user context
586-
* @param {[key: string]: boolean} options Optional map of decide options
587-
* @returns {DecisionResponse<DecisionObj>[]} DecisionResponse containing an object with experiment, variation, and decisionSource properties and decide reasons
584+
* Determines variations for the specified feature flags.
585+
*
586+
* @param {ProjectConfig} configObj - The parsed project configuration object.
587+
* @param {FeatureFlag[]} featureFlags - The feature flags for which variations are to be determined.
588+
* @param {OptimizelyUserContext} user - The user context associated with this decision.
589+
* @param {Record<string, boolean>} options - An optional map of decision options.
590+
* @returns {DecisionResponse<DecisionObj>[]} - An array of DecisionResponse containing objects with
591+
* experiment, variation, decisionSource properties, and decision reasons.
588592
*/
589593
getVariationsForFeatureList(configObj: ProjectConfig,
590594
featureFlags: FeatureFlag[],
@@ -1200,7 +1204,6 @@ export class DecisionService {
12001204
};
12011205
}
12021206
const decisionVariation = this.resolveVariation(configObj, rule, user, userProfileTracker);
1203-
// const decisionVariation = this.getVariation(configObj,rule, user )
12041207
decideReasons.push(...decisionVariation.reasons);
12051208
const variationKey = decisionVariation.result;
12061209

lib/optimizely/index.ts

Lines changed: 97 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ type InputKey = 'feature_key' | 'user_id' | 'variable_key' | 'experiment_key' |
7979

8080
type StringInputs = Partial<Record<InputKey, unknown>>;
8181

82+
type DecisionReasons = (string | number)[];
83+
8284
export default class Optimizely implements Client {
8385
private isOptimizelyConfigValid: boolean;
8486
private disposeOnUpdate: (() => void) | null;
@@ -1526,65 +1528,27 @@ export default class Optimizely implements Client {
15261528
}
15271529

15281530
/**
1529-
* Returns an object of decision results for multiple flag keys and a user context.
1530-
* If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error.
1531-
* The SDK will always return an object of decisions. When it cannot process requests, it will return an empty object after logging the errors.
1532-
* @param {OptimizelyUserContext} user A user context associated with this OptimizelyClient
1533-
* @param {string[]} keys An array of flag keys for which decisions will be made.
1534-
* @param {OptimizelyDecideOption[]} options An array of options for decision-making.
1535-
* @return {[key: string]: OptimizelyDecision} An object of decision results mapped by flag keys.
1531+
* Makes a decision for a given feature key.
1532+
*
1533+
* @param {OptimizelyUserContext} user - The user context associated with this Optimizely client.
1534+
* @param {string} key - The feature key for which a decision will be made.
1535+
* @param {DecisionObj} decisionObj - The decision object containing decision details.
1536+
* @param {DecisionReasons[]} reasons - An array of reasons for the decision.
1537+
* @param {Record<string, boolean>} options - A map of options for decision-making.
1538+
* @param {projectConfig.ProjectConfig} configObj - The project configuration object.
1539+
* @returns {OptimizelyDecision} - The decision object for the feature flag.
15361540
*/
1537-
decideForKeys(
1541+
private generateDecision(
15381542
user: OptimizelyUserContext,
1539-
keys: string[],
1540-
options: OptimizelyDecideOption[] = []
1541-
): { [key: string]: OptimizelyDecision } {
1542-
const decisionMap: { [key: string]: OptimizelyDecision } = {};
1543-
const configObj = this.projectConfigManager.getConfig()
1544-
1545-
if (!this.isValidInstance() || !configObj) {
1546-
this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys');
1547-
return decisionMap;
1548-
}
1549-
if (keys.length === 0) {
1550-
return decisionMap;
1551-
}
1552-
1553-
const allDecideOptions = this.getAllDecideOptions(options);
1554-
1555-
for(const key of keys) {
1556-
const feature = configObj.featureKeyMap[key];
1557-
if (!feature) {
1558-
this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, key);
1559-
decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]);
1560-
continue
1561-
}
1562-
1563-
const userId = user.getUserId();
1564-
const attributes = user.getAttributes();
1565-
const reasons: (string | number)[][] = [];
1566-
const forcedDecisionResponse = this.decisionService.findValidatedForcedDecision(configObj, user, key);
1567-
reasons.push(...forcedDecisionResponse.reasons);
1568-
const variation = forcedDecisionResponse.result;
1569-
let decisionObj: DecisionObj;
1570-
1571-
if (variation) {
1572-
decisionObj = {
1573-
experiment: null,
1574-
variation: variation,
1575-
decisionSource: DECISION_SOURCES.FEATURE_TEST,
1576-
};
1577-
} else {
1578-
const decisionVariation = this.decisionService.getVariationForFeature(
1579-
configObj,
1580-
feature,
1581-
user,
1582-
allDecideOptions
1583-
);
1584-
reasons.push(...decisionVariation.reasons);
1585-
decisionObj = decisionVariation.result;
1586-
}
1587-
1543+
key: string,
1544+
decisionObj: DecisionObj,
1545+
reasons: DecisionReasons[],
1546+
options: Record<string, boolean>,
1547+
configObj: projectConfig.ProjectConfig,
1548+
): OptimizelyDecision {
1549+
const userId = user.getUserId()
1550+
const attributes = user.getAttributes()
1551+
const feature = configObj.featureKeyMap[key]
15881552
const decisionSource = decisionObj.decisionSource;
15891553
const experimentKey = decisionObj.experiment?.key ?? null;
15901554
const variationKey = decisionObj.variation?.key ?? null;
@@ -1599,7 +1563,7 @@ export default class Optimizely implements Client {
15991563
const variablesMap: { [key: string]: unknown } = {};
16001564
let decisionEventDispatched = false;
16011565

1602-
if (!allDecideOptions[OptimizelyDecideOption.EXCLUDE_VARIABLES]) {
1566+
if (!options[OptimizelyDecideOption.EXCLUDE_VARIABLES]) {
16031567
feature.variables.forEach(variable => {
16041568
variablesMap[variable.key] = this.getFeatureVariableValueFromVariation(
16051569
key,
@@ -1612,15 +1576,15 @@ export default class Optimizely implements Client {
16121576
}
16131577

16141578
if (
1615-
!allDecideOptions[OptimizelyDecideOption.DISABLE_DECISION_EVENT] &&
1579+
!options[OptimizelyDecideOption.DISABLE_DECISION_EVENT] &&
16161580
(decisionSource === DECISION_SOURCES.FEATURE_TEST ||
16171581
(decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj)))
16181582
) {
16191583
this.sendImpressionEvent(decisionObj, key, userId, flagEnabled, attributes);
16201584
decisionEventDispatched = true;
16211585
}
16221586

1623-
const shouldIncludeReasons = allDecideOptions[OptimizelyDecideOption.INCLUDE_REASONS];
1587+
const shouldIncludeReasons = options[OptimizelyDecideOption.INCLUDE_REASONS];
16241588

16251589
let reportedReasons: string[] = [];
16261590
if (shouldIncludeReasons) {
@@ -1644,16 +1608,87 @@ export default class Optimizely implements Client {
16441608
decisionInfo: featureInfo,
16451609
});
16461610

1647-
if (!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || flagEnabled) {
1648-
decisionMap[key] = {
1611+
return {
16491612
variationKey: variationKey,
16501613
enabled: flagEnabled,
16511614
variables: variablesMap,
16521615
ruleKey: experimentKey,
16531616
flagKey: key,
16541617
userContext: user,
16551618
reasons: reportedReasons,
1619+
};
1620+
}
1621+
1622+
/**
1623+
* Returns an object of decision results for multiple flag keys and a user context.
1624+
* If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error.
1625+
* The SDK will always return an object of decisions. When it cannot process requests, it will return an empty object after logging the errors.
1626+
* @param {OptimizelyUserContext} user A user context associated with this OptimizelyClient
1627+
* @param {string[]} keys An array of flag keys for which decisions will be made.
1628+
* @param {OptimizelyDecideOption[]} options An array of options for decision-making.
1629+
* @return {[key: string]: OptimizelyDecision} An object of decision results mapped by flag keys.
1630+
*/
1631+
decideForKeys(
1632+
user: OptimizelyUserContext,
1633+
keys: string[],
1634+
options: OptimizelyDecideOption[] = []
1635+
): Record<string, OptimizelyDecision> {
1636+
const decisionMap: Record<string, OptimizelyDecision> = {};
1637+
const flagDecisions: Record<string, DecisionObj> = {};
1638+
const decisionReasonsMap: Record<string, DecisionReasons[]> = {};
1639+
const flagsWithoutForcedDecision = [];
1640+
const validKeys = [];
1641+
1642+
const configObj = this.projectConfigManager.getConfig()
1643+
1644+
if (!this.isValidInstance() || !configObj) {
1645+
this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys');
1646+
return decisionMap;
1647+
}
1648+
if (keys.length === 0) {
1649+
return decisionMap;
1650+
}
1651+
1652+
const allDecideOptions = this.getAllDecideOptions(options);
1653+
1654+
for(const key of keys) {
1655+
const feature = configObj.featureKeyMap[key];
1656+
if (!feature) {
1657+
this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, key);
1658+
decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]);
1659+
continue
1660+
}
1661+
1662+
validKeys.push(key);
1663+
const forcedDecisionResponse = this.decisionService.findValidatedForcedDecision(configObj, user, key);
1664+
decisionReasonsMap[key] = forcedDecisionResponse.reasons
1665+
const variation = forcedDecisionResponse.result;
1666+
1667+
if (variation) {
1668+
flagDecisions[key] = {
1669+
experiment: null,
1670+
variation: variation,
1671+
decisionSource: DECISION_SOURCES.FEATURE_TEST,
16561672
};
1673+
} else {
1674+
flagsWithoutForcedDecision.push(feature)
1675+
}
1676+
}
1677+
1678+
const decisionList = this.decisionService.getVariationsForFeatureList(configObj, flagsWithoutForcedDecision, user, allDecideOptions);
1679+
1680+
for(let i = 0; i < flagsWithoutForcedDecision.length; i++) {
1681+
const key = flagsWithoutForcedDecision[i].key;
1682+
const decision = decisionList[i];
1683+
flagDecisions[key] = decision.result;
1684+
decisionReasonsMap[key] = [...decisionReasonsMap[key], ...decision.reasons];
1685+
}
1686+
1687+
for(const validKey of validKeys) {
1688+
const decision = this.generateDecision(user, validKey, flagDecisions[validKey], decisionReasonsMap[validKey], allDecideOptions, configObj);
1689+
1690+
if(!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || decision.enabled) {
1691+
decisionMap[validKey] = decision;
16571692
}
16581693
}
16591694

0 commit comments

Comments
 (0)