@featurevisor/core 1.22.0 → 1.23.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/.eslintcache +1 -1
- package/CHANGELOG.md +22 -0
- package/coverage/clover.xml +146 -142
- package/coverage/coverage-final.json +4 -4
- package/coverage/lcov-report/index.html +21 -21
- package/coverage/lcov-report/lib/builder/allocator.js.html +37 -28
- package/coverage/lcov-report/lib/builder/index.html +10 -10
- package/coverage/lcov-report/lib/builder/revision.js.html +1 -1
- package/coverage/lcov-report/lib/builder/traffic.js.html +46 -46
- package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
- package/coverage/lcov-report/lib/tester/index.html +1 -1
- package/coverage/lcov-report/lib/tester/matrix.js.html +1 -1
- package/coverage/lcov-report/src/builder/allocator.ts.html +40 -28
- package/coverage/lcov-report/src/builder/index.html +10 -10
- package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/src/builder/traffic.ts.html +44 -44
- package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
- package/coverage/lcov-report/src/tester/index.html +1 -1
- package/coverage/lcov-report/src/tester/matrix.ts.html +1 -1
- package/coverage/lcov.info +242 -235
- package/lib/builder/allocator.js +3 -0
- package/lib/builder/allocator.js.map +1 -1
- package/lib/builder/traffic.spec.js +236 -0
- package/lib/builder/traffic.spec.js.map +1 -1
- package/lib/config/projectConfig.d.ts +1 -0
- package/lib/config/projectConfig.js +1 -0
- package/lib/config/projectConfig.js.map +1 -1
- package/lib/linter/featureSchema.js +15 -0
- package/lib/linter/featureSchema.js.map +1 -1
- package/package.json +2 -2
- package/src/builder/allocator.ts +4 -0
- package/src/builder/traffic.spec.ts +259 -0
- package/src/config/projectConfig.ts +3 -0
- package/src/linter/featureSchema.ts +25 -0
|
@@ -47,6 +47,265 @@ describe("core: Traffic", function () {
|
|
|
47
47
|
]);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
it("should allocate traffic for 0-100 weight on two variations", function () {
|
|
51
|
+
const result = getTraffic(
|
|
52
|
+
// parsed variations from YAML
|
|
53
|
+
[
|
|
54
|
+
{
|
|
55
|
+
value: "control",
|
|
56
|
+
weight: 0,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: "treatment",
|
|
60
|
+
weight: 100,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
|
|
64
|
+
// parsed rollouts from YAML
|
|
65
|
+
[
|
|
66
|
+
{
|
|
67
|
+
key: "1",
|
|
68
|
+
segments: "*",
|
|
69
|
+
percentage: 80,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
|
|
73
|
+
// existing feature from previous release
|
|
74
|
+
undefined,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(result).toEqual([
|
|
78
|
+
{
|
|
79
|
+
key: "1",
|
|
80
|
+
segments: "*",
|
|
81
|
+
percentage: 80000,
|
|
82
|
+
allocation: [
|
|
83
|
+
// should be filtered out automatically
|
|
84
|
+
// {
|
|
85
|
+
// variation: "control",
|
|
86
|
+
// range: [0, 0],
|
|
87
|
+
// },
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
variation: "treatment",
|
|
91
|
+
range: [0, 80000],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should allocate traffic for 0-100 weight on two variations, with rule percentage going from 80% to 100%", function () {
|
|
99
|
+
const result = getTraffic(
|
|
100
|
+
// parsed variations from YAML
|
|
101
|
+
[
|
|
102
|
+
{
|
|
103
|
+
value: "control",
|
|
104
|
+
weight: 0,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
value: "treatment",
|
|
108
|
+
weight: 100,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
|
|
112
|
+
// parsed rollouts from YAML
|
|
113
|
+
[
|
|
114
|
+
{
|
|
115
|
+
key: "1",
|
|
116
|
+
segments: "*",
|
|
117
|
+
percentage: 100,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
|
|
121
|
+
// existing feature from previous release
|
|
122
|
+
{
|
|
123
|
+
variations: [
|
|
124
|
+
{
|
|
125
|
+
value: "control",
|
|
126
|
+
weight: 0,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
value: "treatment",
|
|
130
|
+
weight: 80,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
traffic: [
|
|
134
|
+
{
|
|
135
|
+
key: "1",
|
|
136
|
+
percentage: 80000,
|
|
137
|
+
allocation: [
|
|
138
|
+
{
|
|
139
|
+
variation: "treatment",
|
|
140
|
+
range: [0, 80000],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(result).toEqual([
|
|
149
|
+
{
|
|
150
|
+
key: "1",
|
|
151
|
+
segments: "*",
|
|
152
|
+
percentage: 100000,
|
|
153
|
+
allocation: [
|
|
154
|
+
// should be filtered out automatically
|
|
155
|
+
// {
|
|
156
|
+
// variation: "control",
|
|
157
|
+
// range: [0, 0],
|
|
158
|
+
// },
|
|
159
|
+
|
|
160
|
+
{
|
|
161
|
+
variation: "treatment",
|
|
162
|
+
range: [0, 100000],
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should allocate traffic for 0-100 weight on two variations, with rule percentage going from 100% to 80%", function () {
|
|
170
|
+
const result = getTraffic(
|
|
171
|
+
// parsed variations from YAML
|
|
172
|
+
[
|
|
173
|
+
{
|
|
174
|
+
value: "control",
|
|
175
|
+
weight: 0,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
value: "treatment",
|
|
179
|
+
weight: 100,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
|
|
183
|
+
// parsed rollouts from YAML
|
|
184
|
+
[
|
|
185
|
+
{
|
|
186
|
+
key: "1",
|
|
187
|
+
segments: "*",
|
|
188
|
+
percentage: 80,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
|
|
192
|
+
// existing feature from previous release
|
|
193
|
+
{
|
|
194
|
+
variations: [
|
|
195
|
+
{
|
|
196
|
+
value: "control",
|
|
197
|
+
weight: 0,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
value: "treatment",
|
|
201
|
+
weight: 80,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
traffic: [
|
|
205
|
+
{
|
|
206
|
+
key: "1",
|
|
207
|
+
percentage: 100000,
|
|
208
|
+
allocation: [
|
|
209
|
+
{
|
|
210
|
+
variation: "treatment",
|
|
211
|
+
range: [0, 100000],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
expect(result).toEqual([
|
|
220
|
+
{
|
|
221
|
+
key: "1",
|
|
222
|
+
segments: "*",
|
|
223
|
+
percentage: 80000,
|
|
224
|
+
allocation: [
|
|
225
|
+
// should be filtered out automatically
|
|
226
|
+
// {
|
|
227
|
+
// variation: "control",
|
|
228
|
+
// range: [0, 0],
|
|
229
|
+
// },
|
|
230
|
+
|
|
231
|
+
{
|
|
232
|
+
variation: "treatment",
|
|
233
|
+
range: [0, 80000],
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
]);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should allocate traffic for existing variations state of 0-100 weights", function () {
|
|
241
|
+
const result = getTraffic(
|
|
242
|
+
// parsed variations from YAML
|
|
243
|
+
[
|
|
244
|
+
{
|
|
245
|
+
value: "control",
|
|
246
|
+
weight: 50,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
value: "treatment",
|
|
250
|
+
weight: 50,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
|
|
254
|
+
// parsed rollouts from YAML
|
|
255
|
+
[
|
|
256
|
+
{
|
|
257
|
+
key: "1",
|
|
258
|
+
segments: "*",
|
|
259
|
+
percentage: 80,
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
|
|
263
|
+
// existing feature from previous release
|
|
264
|
+
{
|
|
265
|
+
variations: [
|
|
266
|
+
{
|
|
267
|
+
value: "control",
|
|
268
|
+
weight: 0,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
value: "treatment",
|
|
272
|
+
weight: 80,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
traffic: [
|
|
276
|
+
{
|
|
277
|
+
key: "1",
|
|
278
|
+
percentage: 80000,
|
|
279
|
+
allocation: [
|
|
280
|
+
{
|
|
281
|
+
variation: "treatment",
|
|
282
|
+
range: [0, 80000],
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
expect(result).toEqual([
|
|
291
|
+
{
|
|
292
|
+
key: "1",
|
|
293
|
+
segments: "*",
|
|
294
|
+
percentage: 80000,
|
|
295
|
+
allocation: [
|
|
296
|
+
{
|
|
297
|
+
variation: "control",
|
|
298
|
+
range: [0, 40000],
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
variation: "treatment",
|
|
302
|
+
range: [40000, 80000],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
]);
|
|
307
|
+
});
|
|
308
|
+
|
|
50
309
|
it("should allocate traffic for 50-50 weight on two variations", function () {
|
|
51
310
|
const result = getTraffic(
|
|
52
311
|
// parsed variations from YAML
|
|
@@ -44,6 +44,7 @@ export interface ProjectConfig {
|
|
|
44
44
|
prettyDatafile: boolean;
|
|
45
45
|
stringify: boolean;
|
|
46
46
|
siteExportDirectoryPath: string;
|
|
47
|
+
enforceCatchAllRule?: boolean;
|
|
47
48
|
adapter: any; // @TODO: type this properly later
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -70,6 +71,8 @@ export function getProjectConfig(rootDirectoryPath: string): ProjectConfig {
|
|
|
70
71
|
stateDirectoryPath: path.join(rootDirectoryPath, STATE_DIRECTORY_NAME),
|
|
71
72
|
outputDirectoryPath: path.join(rootDirectoryPath, OUTPUT_DIRECTORY_NAME),
|
|
72
73
|
siteExportDirectoryPath: path.join(rootDirectoryPath, SITE_EXPORT_DIRECTORY_NAME),
|
|
74
|
+
|
|
75
|
+
enforceCatchAllRule: false,
|
|
73
76
|
};
|
|
74
77
|
|
|
75
78
|
const configModulePath = path.join(rootDirectoryPath, CONFIG_MODULE_NAME);
|
|
@@ -87,6 +87,16 @@ export function getFeatureZodSchema(
|
|
|
87
87
|
})
|
|
88
88
|
.strict(),
|
|
89
89
|
)
|
|
90
|
+
|
|
91
|
+
// must have at least one rule
|
|
92
|
+
.refine(
|
|
93
|
+
(value) => value.length > 0,
|
|
94
|
+
() => ({
|
|
95
|
+
message: "Must have at least one rule",
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// duplicate rules
|
|
90
100
|
.refine(
|
|
91
101
|
(value) => {
|
|
92
102
|
const keys = value.map((v) => v.key);
|
|
@@ -95,6 +105,21 @@ export function getFeatureZodSchema(
|
|
|
95
105
|
(value) => ({
|
|
96
106
|
message: "Duplicate rule keys found: " + value.map((v) => v.key).join(", "),
|
|
97
107
|
}),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
// enforce catch-all rule
|
|
111
|
+
.refine(
|
|
112
|
+
(value) => {
|
|
113
|
+
if (!projectConfig.enforceCatchAllRule) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const hasCatchAllAsLastRule = value[value.length - 1].segments === "*";
|
|
118
|
+
return hasCatchAllAsLastRule;
|
|
119
|
+
},
|
|
120
|
+
() => ({
|
|
121
|
+
message: `Missing catch-all rule with \`segments: "*"\` at the end`,
|
|
122
|
+
}),
|
|
98
123
|
),
|
|
99
124
|
force: z
|
|
100
125
|
.array(
|