@featurevisor/sdk 2.17.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/evaluate.ts CHANGED
@@ -35,7 +35,8 @@ export enum EvaluationReason {
35
35
  VARIABLE_NOT_FOUND = "variable_not_found", // variable's schema is not defined in the feature
36
36
  VARIABLE_DEFAULT = "variable_default", // default variable value used
37
37
  VARIABLE_DISABLED = "variable_disabled", // feature is disabled, and variable's disabledValue is used
38
- VARIABLE_OVERRIDE = "variable_override", // variable overridden from inside a variation
38
+ VARIABLE_OVERRIDE_VARIATION = "variable_override_variation", // variable overridden from inside a variation
39
+ VARIABLE_OVERRIDE_RULE = "variable_override_rule", // variable overridden from inside a rule
39
40
 
40
41
  // common
41
42
  NO_MATCH = "no_match", // no rules matched
@@ -75,6 +76,7 @@ export interface Evaluation {
75
76
  variableKey?: VariableKey;
76
77
  variableValue?: VariableValue;
77
78
  variableSchema?: ResolvedVariableSchema;
79
+ variableOverrideIndex?: number;
78
80
  }
79
81
 
80
82
  export interface EvaluateDependencies {
@@ -688,27 +690,76 @@ export function evaluate(options: EvaluateOptions): Evaluation {
688
690
  // variable
689
691
  if (type === "variable" && variableKey) {
690
692
  // override from rule
691
- if (
692
- matchedTraffic &&
693
- matchedTraffic.variables &&
694
- typeof matchedTraffic.variables[variableKey] !== "undefined"
695
- ) {
696
- evaluation = {
697
- type,
698
- featureKey,
699
- reason: EvaluationReason.RULE,
700
- bucketKey,
701
- bucketValue,
702
- ruleKey: matchedTraffic.key,
703
- traffic: matchedTraffic,
704
- variableKey,
705
- variableSchema,
706
- variableValue: matchedTraffic.variables[variableKey],
707
- };
693
+ if (matchedTraffic) {
694
+ // "variableOverrides"
695
+ if (matchedTraffic.variableOverrides && matchedTraffic.variableOverrides[variableKey]) {
696
+ const overrides = matchedTraffic.variableOverrides[variableKey];
697
+
698
+ const overrideIndex = overrides.findIndex((o) => {
699
+ if (o.conditions) {
700
+ return datafileReader.allConditionsAreMatched(
701
+ typeof o.conditions === "string" && o.conditions !== "*"
702
+ ? JSON.parse(o.conditions)
703
+ : o.conditions,
704
+ context,
705
+ );
706
+ }
708
707
 
709
- logger.debug("override from rule", evaluation);
708
+ if (o.segments) {
709
+ return datafileReader.allSegmentsAreMatched(
710
+ datafileReader.parseSegmentsIfStringified(o.segments),
711
+ context,
712
+ );
713
+ }
710
714
 
711
- return evaluation;
715
+ return false;
716
+ });
717
+
718
+ if (overrideIndex !== -1) {
719
+ const override = overrides[overrideIndex];
720
+
721
+ evaluation = {
722
+ type,
723
+ featureKey,
724
+ reason: EvaluationReason.VARIABLE_OVERRIDE_RULE,
725
+ bucketKey,
726
+ bucketValue,
727
+ ruleKey: matchedTraffic?.key,
728
+ traffic: matchedTraffic,
729
+ variableKey,
730
+ variableSchema,
731
+ variableValue: override.value,
732
+ variableOverrideIndex: overrideIndex,
733
+ };
734
+
735
+ logger.debug("variable override from rule", evaluation);
736
+
737
+ return evaluation;
738
+ }
739
+ }
740
+
741
+ // from "variables"
742
+ if (
743
+ matchedTraffic.variables &&
744
+ typeof matchedTraffic.variables[variableKey] !== "undefined"
745
+ ) {
746
+ evaluation = {
747
+ type,
748
+ featureKey,
749
+ reason: EvaluationReason.RULE,
750
+ bucketKey,
751
+ bucketValue,
752
+ ruleKey: matchedTraffic.key,
753
+ traffic: matchedTraffic,
754
+ variableKey,
755
+ variableSchema,
756
+ variableValue: matchedTraffic.variables[variableKey],
757
+ };
758
+
759
+ logger.debug("override from rule", evaluation);
760
+
761
+ return evaluation;
762
+ }
712
763
  }
713
764
 
714
765
  // check variations
@@ -728,7 +779,7 @@ export function evaluate(options: EvaluateOptions): Evaluation {
728
779
  if (variation && variation.variableOverrides && variation.variableOverrides[variableKey]) {
729
780
  const overrides = variation.variableOverrides[variableKey];
730
781
 
731
- const override = overrides.find((o) => {
782
+ const overrideIndex = overrides.findIndex((o) => {
732
783
  if (o.conditions) {
733
784
  return datafileReader.allConditionsAreMatched(
734
785
  typeof o.conditions === "string" && o.conditions !== "*"
@@ -748,11 +799,13 @@ export function evaluate(options: EvaluateOptions): Evaluation {
748
799
  return false;
749
800
  });
750
801
 
751
- if (override) {
802
+ if (overrideIndex !== -1) {
803
+ const override = overrides[overrideIndex];
804
+
752
805
  evaluation = {
753
806
  type,
754
807
  featureKey,
755
- reason: EvaluationReason.VARIABLE_OVERRIDE,
808
+ reason: EvaluationReason.VARIABLE_OVERRIDE_VARIATION,
756
809
  bucketKey,
757
810
  bucketValue,
758
811
  ruleKey: matchedTraffic?.key,
@@ -760,9 +813,10 @@ export function evaluate(options: EvaluateOptions): Evaluation {
760
813
  variableKey,
761
814
  variableSchema,
762
815
  variableValue: override.value,
816
+ variableOverrideIndex: overrideIndex,
763
817
  };
764
818
 
765
- logger.debug("variable override", evaluation);
819
+ logger.debug("variable override from variation", evaluation);
766
820
 
767
821
  return evaluation;
768
822
  }
@@ -1147,6 +1147,137 @@ describe("sdk: instance", function () {
1147
1147
  ).toEqual("orange");
1148
1148
  });
1149
1149
 
1150
+ it("should apply rule variableOverrides on top of rule variables", function () {
1151
+ const sdk = createInstance({
1152
+ datafile: {
1153
+ schemaVersion: "2",
1154
+ revision: "1.0",
1155
+ segments: {
1156
+ germany: {
1157
+ key: "germany",
1158
+ conditions: JSON.stringify([
1159
+ {
1160
+ attribute: "country",
1161
+ operator: "equals",
1162
+ value: "de",
1163
+ },
1164
+ ]),
1165
+ },
1166
+ mobile: {
1167
+ key: "mobile",
1168
+ conditions: JSON.stringify([
1169
+ {
1170
+ attribute: "device",
1171
+ operator: "equals",
1172
+ value: "mobile",
1173
+ },
1174
+ ]),
1175
+ },
1176
+ },
1177
+ features: {
1178
+ test: {
1179
+ key: "test",
1180
+ bucketBy: "userId",
1181
+ variablesSchema: {
1182
+ config: {
1183
+ key: "config",
1184
+ type: "object",
1185
+ defaultValue: {
1186
+ source: "default",
1187
+ nested: { value: 0 },
1188
+ },
1189
+ },
1190
+ },
1191
+ traffic: [
1192
+ {
1193
+ key: "germany",
1194
+ segments: "germany",
1195
+ percentage: 100000,
1196
+ variables: {
1197
+ config: {
1198
+ source: "rule",
1199
+ nested: { value: 10 },
1200
+ flag: true,
1201
+ },
1202
+ },
1203
+ variableOverrides: {
1204
+ config: [
1205
+ {
1206
+ segments: "mobile",
1207
+ value: {
1208
+ source: "rule",
1209
+ nested: { value: 20 },
1210
+ flag: true,
1211
+ },
1212
+ },
1213
+ {
1214
+ conditions: [
1215
+ {
1216
+ attribute: "country",
1217
+ operator: "equals",
1218
+ value: "de",
1219
+ },
1220
+ ],
1221
+ value: {
1222
+ source: "rule",
1223
+ nested: { value: 30 },
1224
+ flag: true,
1225
+ },
1226
+ },
1227
+ ],
1228
+ },
1229
+ },
1230
+ {
1231
+ key: "everyone",
1232
+ segments: "*",
1233
+ percentage: 100000,
1234
+ variables: {
1235
+ config: {
1236
+ source: "everyone",
1237
+ nested: { value: 1 },
1238
+ },
1239
+ },
1240
+ },
1241
+ ],
1242
+ },
1243
+ },
1244
+ },
1245
+ });
1246
+
1247
+ expect(
1248
+ sdk.getVariableObject("test", "config", {
1249
+ userId: "user-1",
1250
+ country: "de",
1251
+ }),
1252
+ ).toEqual({
1253
+ source: "rule",
1254
+ nested: { value: 30 },
1255
+ flag: true,
1256
+ });
1257
+
1258
+ expect(
1259
+ sdk.getVariableObject("test", "config", {
1260
+ userId: "user-1",
1261
+ country: "de",
1262
+ device: "mobile",
1263
+ }),
1264
+ ).toEqual({
1265
+ source: "rule",
1266
+ nested: { value: 20 },
1267
+ flag: true,
1268
+ });
1269
+
1270
+ expect(
1271
+ sdk.getVariableObject("test", "config", {
1272
+ userId: "user-1",
1273
+ country: "nl",
1274
+ }),
1275
+ ).toEqual({
1276
+ source: "everyone",
1277
+ nested: { value: 1 },
1278
+ });
1279
+ });
1280
+
1150
1281
  describe("array and object variables", function () {
1151
1282
  const arrayAndObjectDatafile: DatafileContent = {
1152
1283
  schemaVersion: "2",