@featurevisor/core 2.18.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/CHANGELOG.md +11 -0
- package/coverage/clover.xml +1581 -1119
- package/coverage/coverage-final.json +29 -22
- package/coverage/lcov-report/builder/allocator.ts.html +26 -26
- package/coverage/lcov-report/builder/buildDatafile.ts.html +2017 -0
- package/coverage/lcov-report/builder/buildScopedConditions.ts.html +35 -35
- package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +213 -45
- package/coverage/lcov-report/builder/buildScopedSegments.ts.html +28 -28
- package/coverage/lcov-report/builder/convertToV1.ts.html +583 -0
- package/coverage/lcov-report/builder/getFeatureRanges.ts.html +268 -0
- package/coverage/lcov-report/builder/hashes.ts.html +412 -0
- package/coverage/lcov-report/builder/index.html +90 -30
- package/coverage/lcov-report/builder/mutateVariables.ts.html +66 -45
- package/coverage/lcov-report/builder/mutator.ts.html +95 -95
- package/coverage/lcov-report/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/builder/traffic.ts.html +60 -57
- package/coverage/lcov-report/config/index.html +1 -1
- package/coverage/lcov-report/config/index.ts.html +2 -2
- package/coverage/lcov-report/config/projectConfig.ts.html +28 -28
- package/coverage/lcov-report/datasource/adapter.ts.html +2 -2
- package/coverage/lcov-report/datasource/datasource.ts.html +4 -4
- package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +34 -34
- package/coverage/lcov-report/datasource/index.html +1 -1
- package/coverage/lcov-report/datasource/index.ts.html +1 -1
- package/coverage/lcov-report/index.html +39 -39
- package/coverage/lcov-report/linter/attributeSchema.ts.html +1 -1
- package/coverage/lcov-report/linter/checkCircularDependency.ts.html +1 -1
- package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +1 -1
- package/coverage/lcov-report/linter/conditionSchema.ts.html +17 -17
- package/coverage/lcov-report/linter/featureSchema.ts.html +686 -401
- package/coverage/lcov-report/linter/groupSchema.ts.html +1 -1
- package/coverage/lcov-report/linter/index.html +27 -27
- package/coverage/lcov-report/linter/lintProject.ts.html +25 -25
- package/coverage/lcov-report/linter/mutationNotation.ts.html +191 -122
- package/coverage/lcov-report/linter/printError.ts.html +1 -1
- package/coverage/lcov-report/linter/schema.ts.html +72 -72
- package/coverage/lcov-report/linter/segmentSchema.ts.html +1 -1
- package/coverage/lcov-report/linter/testSchema.ts.html +5 -5
- package/coverage/lcov-report/list/index.html +1 -1
- package/coverage/lcov-report/list/matrix.ts.html +1 -1
- package/coverage/lcov-report/parsers/index.html +1 -1
- package/coverage/lcov-report/parsers/index.ts.html +4 -4
- package/coverage/lcov-report/parsers/json.ts.html +2 -2
- package/coverage/lcov-report/parsers/yml.ts.html +6 -6
- package/coverage/lcov-report/tester/cliFormat.ts.html +1 -1
- package/coverage/lcov-report/tester/helpers.ts.html +1 -1
- package/coverage/lcov-report/tester/index.html +1 -1
- package/coverage/lcov-report/utils/extractKeys.ts.html +493 -0
- package/coverage/lcov-report/utils/git.ts.html +3 -3
- package/coverage/lcov-report/utils/index.html +54 -9
- package/coverage/lcov-report/utils/index.ts.html +94 -0
- package/coverage/lcov-report/utils/pretty.ts.html +121 -0
- package/coverage/lcov.info +2955 -2095
- package/lib/builder/buildDatafile.js +50 -3
- package/lib/builder/buildDatafile.js.map +1 -1
- package/lib/builder/buildDatafile.spec.d.ts +1 -0
- package/lib/builder/buildDatafile.spec.js +233 -0
- package/lib/builder/buildDatafile.spec.js.map +1 -0
- package/lib/builder/buildScopedDatafile.js +37 -1
- package/lib/builder/buildScopedDatafile.js.map +1 -1
- package/lib/builder/buildScopedDatafile.spec.js +289 -0
- package/lib/builder/buildScopedDatafile.spec.js.map +1 -1
- package/lib/builder/mutateVariables.d.ts +1 -1
- package/lib/builder/mutateVariables.js +4 -1
- package/lib/builder/mutateVariables.js.map +1 -1
- package/lib/builder/mutateVariables.spec.js +29 -0
- package/lib/builder/mutateVariables.spec.js.map +1 -1
- package/lib/builder/traffic.js +1 -0
- package/lib/builder/traffic.js.map +1 -1
- package/lib/builder/traffic.spec.js +26 -0
- package/lib/builder/traffic.spec.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +228 -22
- package/lib/linter/featureSchema.js +137 -76
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/featureSchema.spec.js +175 -0
- package/lib/linter/featureSchema.spec.js.map +1 -1
- package/lib/linter/mutationNotation.js +35 -10
- package/lib/linter/mutationNotation.js.map +1 -1
- package/lib/linter/mutationNotation.spec.js +31 -0
- package/lib/linter/mutationNotation.spec.js.map +1 -1
- package/package.json +5 -5
- package/src/builder/buildDatafile.spec.ts +267 -0
- package/src/builder/buildDatafile.ts +85 -4
- package/src/builder/buildScopedDatafile.spec.ts +330 -0
- package/src/builder/buildScopedDatafile.ts +57 -1
- package/src/builder/mutateVariables.spec.ts +45 -0
- package/src/builder/mutateVariables.ts +7 -0
- package/src/builder/traffic.spec.ts +33 -0
- package/src/builder/traffic.ts +1 -0
- package/src/linter/featureSchema.spec.ts +197 -0
- package/src/linter/featureSchema.ts +204 -109
- package/src/linter/mutationNotation.spec.ts +39 -0
- package/src/linter/mutationNotation.ts +30 -7
|
@@ -956,6 +956,336 @@ describe("core: buildScopedDatafile", function () {
|
|
|
956
956
|
});
|
|
957
957
|
});
|
|
958
958
|
|
|
959
|
+
describe("features with traffic and variableOverrides", function () {
|
|
960
|
+
test("traffic variableOverride with matching segments becomes *", function () {
|
|
961
|
+
const datafile: DatafileContent = {
|
|
962
|
+
schemaVersion: "2",
|
|
963
|
+
revision: "unknown",
|
|
964
|
+
segments: {
|
|
965
|
+
mobile: {
|
|
966
|
+
conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
features: {
|
|
970
|
+
feature1: {
|
|
971
|
+
bucketBy: ["userId"],
|
|
972
|
+
traffic: [
|
|
973
|
+
{
|
|
974
|
+
key: "rule1",
|
|
975
|
+
segments: "*",
|
|
976
|
+
percentage: 100000,
|
|
977
|
+
variableOverrides: {
|
|
978
|
+
config: [{ segments: "mobile", value: { source: "mobile" } }],
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
],
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
const result = buildScopedDatafile(datafile, { device: "mobile" });
|
|
987
|
+
|
|
988
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
|
|
989
|
+
"*",
|
|
990
|
+
);
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
test("traffic variableOverride with non-matching segments remains", function () {
|
|
994
|
+
const datafile: DatafileContent = {
|
|
995
|
+
schemaVersion: "2",
|
|
996
|
+
revision: "unknown",
|
|
997
|
+
segments: {
|
|
998
|
+
mobile: {
|
|
999
|
+
conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
|
|
1000
|
+
},
|
|
1001
|
+
},
|
|
1002
|
+
features: {
|
|
1003
|
+
feature1: {
|
|
1004
|
+
bucketBy: ["userId"],
|
|
1005
|
+
traffic: [
|
|
1006
|
+
{
|
|
1007
|
+
key: "rule1",
|
|
1008
|
+
segments: "*",
|
|
1009
|
+
percentage: 100000,
|
|
1010
|
+
variableOverrides: {
|
|
1011
|
+
config: [{ segments: "mobile", value: { source: "mobile" } }],
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
],
|
|
1015
|
+
},
|
|
1016
|
+
},
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
const result = buildScopedDatafile(datafile, { device: "desktop" });
|
|
1020
|
+
|
|
1021
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
|
|
1022
|
+
"mobile",
|
|
1023
|
+
);
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
test("traffic variableOverride with matching conditions becomes *", function () {
|
|
1027
|
+
const datafile: DatafileContent = {
|
|
1028
|
+
schemaVersion: "2",
|
|
1029
|
+
revision: "unknown",
|
|
1030
|
+
segments: {},
|
|
1031
|
+
features: {
|
|
1032
|
+
feature1: {
|
|
1033
|
+
bucketBy: ["userId"],
|
|
1034
|
+
traffic: [
|
|
1035
|
+
{
|
|
1036
|
+
key: "rule1",
|
|
1037
|
+
segments: "*",
|
|
1038
|
+
percentage: 100000,
|
|
1039
|
+
variableOverrides: {
|
|
1040
|
+
config: [
|
|
1041
|
+
{
|
|
1042
|
+
conditions: [{ attribute: "country", operator: "equals", value: "nl" }],
|
|
1043
|
+
value: { source: "nl" },
|
|
1044
|
+
},
|
|
1045
|
+
],
|
|
1046
|
+
},
|
|
1047
|
+
},
|
|
1048
|
+
],
|
|
1049
|
+
},
|
|
1050
|
+
},
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
const result = buildScopedDatafile(datafile, { country: "nl" });
|
|
1054
|
+
|
|
1055
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[0].conditions).toEqual(
|
|
1056
|
+
"*",
|
|
1057
|
+
);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
test("traffic variableOverride with non-matching conditions remains", function () {
|
|
1061
|
+
const datafile: DatafileContent = {
|
|
1062
|
+
schemaVersion: "2",
|
|
1063
|
+
revision: "unknown",
|
|
1064
|
+
segments: {},
|
|
1065
|
+
features: {
|
|
1066
|
+
feature1: {
|
|
1067
|
+
bucketBy: ["userId"],
|
|
1068
|
+
traffic: [
|
|
1069
|
+
{
|
|
1070
|
+
key: "rule1",
|
|
1071
|
+
segments: "*",
|
|
1072
|
+
percentage: 100000,
|
|
1073
|
+
variableOverrides: {
|
|
1074
|
+
config: [
|
|
1075
|
+
{
|
|
1076
|
+
conditions: [{ attribute: "country", operator: "equals", value: "nl" }],
|
|
1077
|
+
value: { source: "nl" },
|
|
1078
|
+
},
|
|
1079
|
+
],
|
|
1080
|
+
},
|
|
1081
|
+
},
|
|
1082
|
+
],
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
const result = buildScopedDatafile(datafile, { country: "de" });
|
|
1088
|
+
|
|
1089
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[0].conditions).toEqual([
|
|
1090
|
+
{ attribute: "country", operator: "equals", value: "nl" },
|
|
1091
|
+
]);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
test("traffic variableOverride supports array/group segments and conditions", function () {
|
|
1095
|
+
const datafile: DatafileContent = {
|
|
1096
|
+
schemaVersion: "2",
|
|
1097
|
+
revision: "unknown",
|
|
1098
|
+
segments: {
|
|
1099
|
+
mobile: {
|
|
1100
|
+
conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
|
|
1101
|
+
},
|
|
1102
|
+
touch: {
|
|
1103
|
+
conditions: [{ attribute: "touch", operator: "equals", value: true }],
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
features: {
|
|
1107
|
+
feature1: {
|
|
1108
|
+
bucketBy: ["userId"],
|
|
1109
|
+
traffic: [
|
|
1110
|
+
{
|
|
1111
|
+
key: "rule1",
|
|
1112
|
+
segments: "*",
|
|
1113
|
+
percentage: 100000,
|
|
1114
|
+
variableOverrides: {
|
|
1115
|
+
config: [
|
|
1116
|
+
{
|
|
1117
|
+
segments: { and: ["mobile", "touch"] },
|
|
1118
|
+
value: { source: "group-segments" },
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
conditions: {
|
|
1122
|
+
and: [
|
|
1123
|
+
{ attribute: "country", operator: "equals", value: "nl" },
|
|
1124
|
+
{ attribute: "tier", operator: "equals", value: "premium" },
|
|
1125
|
+
],
|
|
1126
|
+
},
|
|
1127
|
+
value: { source: "group-conditions" },
|
|
1128
|
+
},
|
|
1129
|
+
],
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
1132
|
+
],
|
|
1133
|
+
},
|
|
1134
|
+
},
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
const result = buildScopedDatafile(datafile, {
|
|
1138
|
+
device: "mobile",
|
|
1139
|
+
touch: true,
|
|
1140
|
+
country: "nl",
|
|
1141
|
+
tier: "premium",
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
|
|
1145
|
+
"*",
|
|
1146
|
+
);
|
|
1147
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[1].conditions).toEqual(
|
|
1148
|
+
"*",
|
|
1149
|
+
);
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
test("traffic variableOverride supports stringified segments and conditions", function () {
|
|
1153
|
+
const datafile: DatafileContent = {
|
|
1154
|
+
schemaVersion: "2",
|
|
1155
|
+
revision: "unknown",
|
|
1156
|
+
segments: {
|
|
1157
|
+
mobile: {
|
|
1158
|
+
conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
|
|
1159
|
+
},
|
|
1160
|
+
touch: {
|
|
1161
|
+
conditions: [{ attribute: "touch", operator: "equals", value: true }],
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
features: {
|
|
1165
|
+
feature1: {
|
|
1166
|
+
bucketBy: ["userId"],
|
|
1167
|
+
traffic: [
|
|
1168
|
+
{
|
|
1169
|
+
key: "rule1",
|
|
1170
|
+
segments: "*",
|
|
1171
|
+
percentage: 100000,
|
|
1172
|
+
variableOverrides: {
|
|
1173
|
+
config: [
|
|
1174
|
+
{
|
|
1175
|
+
segments: JSON.stringify({ and: ["mobile", "touch"] }),
|
|
1176
|
+
value: { source: "stringified-segments" },
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
conditions: JSON.stringify([
|
|
1180
|
+
{ attribute: "country", operator: "equals", value: "nl" },
|
|
1181
|
+
]),
|
|
1182
|
+
value: { source: "stringified-conditions" },
|
|
1183
|
+
},
|
|
1184
|
+
],
|
|
1185
|
+
},
|
|
1186
|
+
},
|
|
1187
|
+
],
|
|
1188
|
+
},
|
|
1189
|
+
},
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
const result = buildScopedDatafile(datafile, {
|
|
1193
|
+
device: "mobile",
|
|
1194
|
+
touch: true,
|
|
1195
|
+
country: "nl",
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
|
|
1199
|
+
"*",
|
|
1200
|
+
);
|
|
1201
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[1].conditions).toEqual(
|
|
1202
|
+
"*",
|
|
1203
|
+
);
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
test("multiple traffic entries still apply consecutive * dedupe", function () {
|
|
1207
|
+
const datafile: DatafileContent = {
|
|
1208
|
+
schemaVersion: "2",
|
|
1209
|
+
revision: "unknown",
|
|
1210
|
+
segments: {
|
|
1211
|
+
web: {
|
|
1212
|
+
conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
|
|
1213
|
+
},
|
|
1214
|
+
mobile: {
|
|
1215
|
+
conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
|
|
1216
|
+
},
|
|
1217
|
+
},
|
|
1218
|
+
features: {
|
|
1219
|
+
feature1: {
|
|
1220
|
+
bucketBy: ["userId"],
|
|
1221
|
+
traffic: [
|
|
1222
|
+
{
|
|
1223
|
+
key: "rule1",
|
|
1224
|
+
segments: "web",
|
|
1225
|
+
percentage: 50000,
|
|
1226
|
+
variableOverrides: {
|
|
1227
|
+
config: [{ segments: "mobile", value: { source: "rule1" } }],
|
|
1228
|
+
},
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
key: "rule2",
|
|
1232
|
+
segments: "web",
|
|
1233
|
+
percentage: 50000,
|
|
1234
|
+
variableOverrides: {
|
|
1235
|
+
config: [{ segments: "mobile", value: { source: "rule2" } }],
|
|
1236
|
+
},
|
|
1237
|
+
},
|
|
1238
|
+
],
|
|
1239
|
+
},
|
|
1240
|
+
},
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
const result = buildScopedDatafile(datafile, {
|
|
1244
|
+
platform: "web",
|
|
1245
|
+
device: "mobile",
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
expect(result.features.feature1.traffic).toHaveLength(1);
|
|
1249
|
+
expect(result.features.feature1.traffic[0].key).toEqual("rule1");
|
|
1250
|
+
expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
|
|
1251
|
+
"*",
|
|
1252
|
+
);
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
test("does not mutate original datafile traffic variableOverrides", function () {
|
|
1256
|
+
const datafile: DatafileContent = {
|
|
1257
|
+
schemaVersion: "2",
|
|
1258
|
+
revision: "unknown",
|
|
1259
|
+
segments: {
|
|
1260
|
+
mobile: {
|
|
1261
|
+
conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
|
|
1262
|
+
},
|
|
1263
|
+
},
|
|
1264
|
+
features: {
|
|
1265
|
+
feature1: {
|
|
1266
|
+
bucketBy: ["userId"],
|
|
1267
|
+
traffic: [
|
|
1268
|
+
{
|
|
1269
|
+
key: "rule1",
|
|
1270
|
+
segments: "*",
|
|
1271
|
+
percentage: 100000,
|
|
1272
|
+
variableOverrides: {
|
|
1273
|
+
config: [{ segments: "mobile", value: { source: "mobile" } }],
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
],
|
|
1277
|
+
},
|
|
1278
|
+
},
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
const original = JSON.stringify(datafile);
|
|
1282
|
+
|
|
1283
|
+
buildScopedDatafile(datafile, { device: "mobile" });
|
|
1284
|
+
|
|
1285
|
+
expect(JSON.stringify(datafile)).toEqual(original);
|
|
1286
|
+
});
|
|
1287
|
+
});
|
|
1288
|
+
|
|
959
1289
|
describe("features with variations and variableOverrides", function () {
|
|
960
1290
|
test("variableOverride with matching segments becomes *", function () {
|
|
961
1291
|
const datafile: DatafileContent = {
|
|
@@ -4,6 +4,23 @@ import { DatafileReader, createLogger } from "@featurevisor/sdk";
|
|
|
4
4
|
import { buildScopedConditions } from "./buildScopedConditions";
|
|
5
5
|
import { buildScopedSegments } from "./buildScopedSegments";
|
|
6
6
|
|
|
7
|
+
function parseIfStringified<T>(value: T): T {
|
|
8
|
+
if (typeof value !== "string" || value === "*") {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const firstChar = value[0];
|
|
13
|
+
if (firstChar !== "{" && firstChar !== "[") {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(value) as T;
|
|
19
|
+
} catch {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
export function buildScopedDatafile(
|
|
8
25
|
originalDatafileContent: DatafileContent,
|
|
9
26
|
context: Context,
|
|
@@ -76,13 +93,52 @@ export function buildScopedDatafile(
|
|
|
76
93
|
|
|
77
94
|
// segments
|
|
78
95
|
if (traffic.segments) {
|
|
96
|
+
const segments = parseIfStringified(traffic.segments);
|
|
79
97
|
feature.traffic[trafficI].segments = buildScopedSegments(
|
|
80
98
|
originalDatafileReader,
|
|
81
|
-
|
|
99
|
+
segments,
|
|
82
100
|
context,
|
|
83
101
|
removeSegments,
|
|
84
102
|
);
|
|
85
103
|
}
|
|
104
|
+
|
|
105
|
+
// variable overrides
|
|
106
|
+
if (traffic.variableOverrides) {
|
|
107
|
+
for (const variableKey in traffic.variableOverrides) {
|
|
108
|
+
const variableOverrides = traffic.variableOverrides[variableKey];
|
|
109
|
+
|
|
110
|
+
for (
|
|
111
|
+
let variableOverrideI = 0;
|
|
112
|
+
variableOverrideI < variableOverrides.length;
|
|
113
|
+
variableOverrideI++
|
|
114
|
+
) {
|
|
115
|
+
const variableOverride = variableOverrides[variableOverrideI];
|
|
116
|
+
|
|
117
|
+
// segments
|
|
118
|
+
if (variableOverride.segments) {
|
|
119
|
+
const segments = parseIfStringified(variableOverride.segments);
|
|
120
|
+
variableOverride.segments = buildScopedSegments(
|
|
121
|
+
originalDatafileReader,
|
|
122
|
+
segments,
|
|
123
|
+
context,
|
|
124
|
+
removeSegments,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// conditions
|
|
129
|
+
if (variableOverride.conditions) {
|
|
130
|
+
const conditions = parseIfStringified(variableOverride.conditions);
|
|
131
|
+
variableOverride.conditions = buildScopedConditions(
|
|
132
|
+
originalDatafileReader,
|
|
133
|
+
conditions,
|
|
134
|
+
context,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
variableOverrides[variableOverrideI] = variableOverride;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
86
142
|
}
|
|
87
143
|
}
|
|
88
144
|
|
|
@@ -987,6 +987,51 @@ describe("mutateVariables", function () {
|
|
|
987
987
|
const result = resolveMutationsForSingleVariable(schema, "foo", {});
|
|
988
988
|
expect(result).toEqual({});
|
|
989
989
|
});
|
|
990
|
+
|
|
991
|
+
test("uses provided baseValue instead of defaultValue for path mutations", function () {
|
|
992
|
+
const schema: Record<string, VariableSchema> = {
|
|
993
|
+
cfg: {
|
|
994
|
+
type: "object",
|
|
995
|
+
defaultValue: { source: "default", nested: { value: 1 } },
|
|
996
|
+
},
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
const result = resolveMutationsForSingleVariable(
|
|
1000
|
+
schema,
|
|
1001
|
+
"cfg",
|
|
1002
|
+
{
|
|
1003
|
+
"nested.value": 2,
|
|
1004
|
+
},
|
|
1005
|
+
{ source: "rule", nested: { value: 10 }, extra: true },
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
expect(result).toEqual({
|
|
1009
|
+
source: "rule",
|
|
1010
|
+
nested: { value: 2 },
|
|
1011
|
+
extra: true,
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
test("baseValue with full replacement key still applies replacement first", function () {
|
|
1016
|
+
const schema: Record<string, VariableSchema> = {
|
|
1017
|
+
cfg: {
|
|
1018
|
+
type: "object",
|
|
1019
|
+
defaultValue: { a: 1, b: 2 },
|
|
1020
|
+
},
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
const result = resolveMutationsForSingleVariable(
|
|
1024
|
+
schema,
|
|
1025
|
+
"cfg",
|
|
1026
|
+
{
|
|
1027
|
+
cfg: { a: 100, c: 300 },
|
|
1028
|
+
c: 999,
|
|
1029
|
+
},
|
|
1030
|
+
{ a: 10, b: 20 },
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
expect(result).toEqual({ a: 100, c: 999 });
|
|
1034
|
+
});
|
|
990
1035
|
});
|
|
991
1036
|
|
|
992
1037
|
describe("does not mutate input", function () {
|
|
@@ -89,14 +89,21 @@ export function resolveMutationsForSingleVariable(
|
|
|
89
89
|
variablesSchema: Record<string, VariableSchema> | undefined,
|
|
90
90
|
variableKey: string,
|
|
91
91
|
overrideValue: VariableValue,
|
|
92
|
+
baseValue?: VariableValue,
|
|
92
93
|
): VariableValue {
|
|
93
94
|
if (!variablesSchema || !variablesSchema[variableKey]) return overrideValue;
|
|
94
95
|
if (overrideValue === null || overrideValue === undefined) return overrideValue;
|
|
95
96
|
if (typeof overrideValue !== "object" || Array.isArray(overrideValue)) {
|
|
96
97
|
return overrideValue;
|
|
97
98
|
}
|
|
99
|
+
|
|
98
100
|
const pathMap = overrideValue as Record<string, VariableValue>;
|
|
99
101
|
const flat: Record<string, VariableValue> = {};
|
|
102
|
+
|
|
103
|
+
if (typeof baseValue !== "undefined") {
|
|
104
|
+
flat[variableKey] = JSON.parse(JSON.stringify(baseValue)) as VariableValue;
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
for (const [k, v] of Object.entries(pathMap)) {
|
|
101
108
|
flat[k === variableKey ? variableKey : variableKey + "." + k] = v;
|
|
102
109
|
}
|
|
@@ -910,4 +910,37 @@ describe("core: Traffic", function () {
|
|
|
910
910
|
},
|
|
911
911
|
]);
|
|
912
912
|
});
|
|
913
|
+
|
|
914
|
+
it("should include variableOverrides from parsed rule in traffic", function () {
|
|
915
|
+
const variableOverrides = {
|
|
916
|
+
config: [
|
|
917
|
+
{
|
|
918
|
+
segments: "mobile",
|
|
919
|
+
value: {
|
|
920
|
+
"layout.width": 900,
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
],
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
const result = getTraffic(
|
|
927
|
+
[
|
|
928
|
+
{
|
|
929
|
+
value: "control",
|
|
930
|
+
weight: 100,
|
|
931
|
+
},
|
|
932
|
+
],
|
|
933
|
+
[
|
|
934
|
+
{
|
|
935
|
+
key: "1",
|
|
936
|
+
segments: "*",
|
|
937
|
+
percentage: 100,
|
|
938
|
+
variableOverrides,
|
|
939
|
+
},
|
|
940
|
+
],
|
|
941
|
+
undefined,
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
expect(result[0].variableOverrides).toEqual(variableOverrides);
|
|
945
|
+
});
|
|
913
946
|
});
|
package/src/builder/traffic.ts
CHANGED