make-mp-data 1.3.4 → 1.4.1
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/.vscode/launch.json +11 -3
- package/.vscode/settings.json +11 -1
- package/README.md +2 -2
- package/chart.js +180 -0
- package/defaults.js +11662 -0
- package/index.js +485 -298
- package/package.json +59 -52
- package/{models → schemas}/complex.js +18 -18
- package/{models → schemas}/foobar.js +1 -1
- package/schemas/funnels.js +221 -0
- package/{models → schemas}/simple.js +10 -10
- package/scratch.mjs +22 -0
- package/testCases.mjs +229 -0
- package/testSoup.mjs +27 -0
- package/tests/e2e.test.js +27 -20
- package/tests/jest.config.js +30 -0
- package/tests/unit.test.js +444 -17
- package/tmp/.gitkeep +0 -0
- package/tsconfig.json +1 -1
- package/types.d.ts +81 -15
- package/utils.js +670 -155
- package/timesoup.js +0 -92
- /package/{models → schemas}/deepNest.js +0 -0
package/tests/unit.test.js
CHANGED
|
@@ -1,19 +1,58 @@
|
|
|
1
1
|
const generate = require('../index.js');
|
|
2
2
|
const dayjs = require("dayjs");
|
|
3
3
|
const utc = require("dayjs/plugin/utc");
|
|
4
|
+
const fs = require('fs');
|
|
4
5
|
const u = require('ak-tools');
|
|
5
6
|
dayjs.extend(utc);
|
|
6
|
-
const { timeSoup } = generate;
|
|
7
7
|
require('dotenv').config();
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const { applySkew,
|
|
10
|
+
boxMullerRandom,
|
|
11
|
+
choose,
|
|
12
|
+
date,
|
|
13
|
+
dates,
|
|
14
|
+
day,
|
|
15
|
+
exhaust,
|
|
16
|
+
generateEmoji,
|
|
17
|
+
getUniqueKeys,
|
|
18
|
+
integer,
|
|
19
|
+
mapToRange,
|
|
20
|
+
person,
|
|
21
|
+
pick,
|
|
22
|
+
range,
|
|
23
|
+
pickAWinner,
|
|
24
|
+
weightedRange,
|
|
25
|
+
enrichArray,
|
|
26
|
+
fixFirstAndLast,
|
|
27
|
+
generateUser,
|
|
28
|
+
openFinder,
|
|
29
|
+
progress,
|
|
30
|
+
shuffleArray,
|
|
31
|
+
shuffleExceptFirst,
|
|
32
|
+
shuffleExceptLast,
|
|
33
|
+
shuffleMiddle,
|
|
34
|
+
shuffleOutside,
|
|
35
|
+
streamCSV,
|
|
36
|
+
streamJSON,
|
|
37
|
+
weighArray,
|
|
38
|
+
weighFunnels,
|
|
39
|
+
buildFileNames,
|
|
40
|
+
TimeSoup,
|
|
41
|
+
getChance,
|
|
42
|
+
initChance,
|
|
43
|
+
validateEventConfig,
|
|
44
|
+
validateTime,
|
|
45
|
+
interruptArray,
|
|
46
|
+
optimizedBoxMuller
|
|
47
|
+
} = require('../utils');
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
describe('timesoup', () => {
|
|
51
|
+
test('always valid times', () => {
|
|
13
52
|
const dates = [];
|
|
14
|
-
for (let i = 0; i <
|
|
15
|
-
const earliest = dayjs().subtract(u.rand(
|
|
16
|
-
dates.push(
|
|
53
|
+
for (let i = 0; i < 10000; i++) {
|
|
54
|
+
const earliest = dayjs().subtract(u.rand(5, 50), 'D');
|
|
55
|
+
dates.push(TimeSoup());
|
|
17
56
|
}
|
|
18
57
|
const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
|
|
19
58
|
const badYear = dates.filter(d => !d.startsWith('202'));
|
|
@@ -24,10 +63,288 @@ describe('timeSoup', () => {
|
|
|
24
63
|
});
|
|
25
64
|
|
|
26
65
|
|
|
66
|
+
describe('names', () => {
|
|
67
|
+
|
|
68
|
+
test('default config', () => {
|
|
69
|
+
const config = { simulationName: 'testSim' };
|
|
70
|
+
const result = buildFileNames(config);
|
|
71
|
+
expect(result.eventFiles).toEqual(['testSim-EVENTS.csv']);
|
|
72
|
+
expect(result.userFiles).toEqual(['testSim-USERS.csv']);
|
|
73
|
+
expect(result.scdFiles).toEqual([]);
|
|
74
|
+
expect(result.groupFiles).toEqual([]);
|
|
75
|
+
expect(result.lookupFiles).toEqual([]);
|
|
76
|
+
expect(result.mirrorFiles).toEqual([]);
|
|
77
|
+
expect(result.folder).toEqual('./');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('json format', () => {
|
|
81
|
+
const config = { simulationName: 'testSim', format: 'json' };
|
|
82
|
+
const result = buildFileNames(config);
|
|
83
|
+
expect(result.eventFiles).toEqual(['testSim-EVENTS.json']);
|
|
84
|
+
expect(result.userFiles).toEqual(['testSim-USERS.json']);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('with scdProps', () => {
|
|
88
|
+
const config = {
|
|
89
|
+
simulationName: 'testSim',
|
|
90
|
+
scdProps: { prop1: {}, prop2: {} }
|
|
91
|
+
};
|
|
92
|
+
const result = buildFileNames(config);
|
|
93
|
+
expect(result.scdFiles).toEqual([
|
|
94
|
+
'testSim-prop1-SCD.csv',
|
|
95
|
+
'testSim-prop2-SCD.csv'
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('with groupKeys', () => {
|
|
100
|
+
const config = {
|
|
101
|
+
simulationName: 'testSim',
|
|
102
|
+
groupKeys: [['group1'], ['group2']]
|
|
103
|
+
};
|
|
104
|
+
const result = buildFileNames(config);
|
|
105
|
+
expect(result.groupFiles).toEqual([
|
|
106
|
+
'testSim-group1-GROUP.csv',
|
|
107
|
+
'testSim-group2-GROUP.csv'
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('with lookupTables', () => {
|
|
112
|
+
const config = {
|
|
113
|
+
simulationName: 'testSim',
|
|
114
|
+
lookupTables: [{ key: 'lookup1' }, { key: 'lookup2' }]
|
|
115
|
+
};
|
|
116
|
+
const result = buildFileNames(config);
|
|
117
|
+
expect(result.lookupFiles).toEqual([
|
|
118
|
+
'testSim-lookup1-LOOKUP.csv',
|
|
119
|
+
'testSim-lookup2-LOOKUP.csv'
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('with mirrorProps', () => {
|
|
124
|
+
const config = {
|
|
125
|
+
simulationName: 'testSim',
|
|
126
|
+
mirrorProps: { prop1: {} }
|
|
127
|
+
};
|
|
128
|
+
const result = buildFileNames(config);
|
|
129
|
+
expect(result.mirrorFiles).toEqual(['testSim-MIRROR.csv']);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('writeToDisk', async () => {
|
|
133
|
+
const config = { simulationName: 'testSim', writeToDisk: true };
|
|
134
|
+
const result = await buildFileNames(config);
|
|
135
|
+
expect(result.folder).toBeDefined();
|
|
136
|
+
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
test('invalid simName', () => {
|
|
141
|
+
const config = { simulationName: 123 };
|
|
142
|
+
expect(() => buildFileNames(config)).toThrow('simName must be a string');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
test('streamJSON: writes to file', async () => {
|
|
147
|
+
const path = 'test.json';
|
|
148
|
+
const data = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
|
|
149
|
+
await streamJSON(path, data);
|
|
150
|
+
const content = fs.readFileSync(path, 'utf8');
|
|
151
|
+
const lines = content.trim().split('\n').map(line => JSON.parse(line));
|
|
152
|
+
expect(lines).toEqual(data);
|
|
153
|
+
fs.unlinkSync(path);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('streamCSV: writes to file', async () => {
|
|
157
|
+
const path = 'test.csv';
|
|
158
|
+
const data = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
|
|
159
|
+
await streamCSV(path, data);
|
|
160
|
+
const content = fs.readFileSync(path, 'utf8');
|
|
161
|
+
const lines = content.trim().split('\n');
|
|
162
|
+
expect(lines.length).toBe(3); // Including header
|
|
163
|
+
fs.unlinkSync(path);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
test('generateUser: works', () => {
|
|
168
|
+
const uuid = { guid: jest.fn().mockReturnValue('uuid-123') };
|
|
169
|
+
const numDays = 30;
|
|
170
|
+
const user = generateUser(numDays);
|
|
171
|
+
expect(user).toHaveProperty('distinct_id');
|
|
172
|
+
expect(user).toHaveProperty('name');
|
|
173
|
+
expect(user).toHaveProperty('email');
|
|
174
|
+
expect(user).toHaveProperty('avatar');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('enrichArray: works', () => {
|
|
178
|
+
const arr = [];
|
|
179
|
+
const enrichedArray = enrichArray(arr);
|
|
180
|
+
enrichedArray.hookPush(1);
|
|
181
|
+
enrichedArray.hookPush(2);
|
|
182
|
+
const match = JSON.stringify(enrichedArray) === JSON.stringify([1, 2]);
|
|
183
|
+
expect(match).toEqual(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
describe('determinism', () => {
|
|
190
|
+
test('initializes RNG with seed from environment variable', () => {
|
|
191
|
+
process.env.SEED = 'test-seed';
|
|
192
|
+
// @ts-ignore
|
|
193
|
+
initChance();
|
|
194
|
+
const chance = getChance();
|
|
195
|
+
expect(chance).toBeDefined();
|
|
196
|
+
expect(chance.random()).toBeGreaterThanOrEqual(0);
|
|
197
|
+
expect(chance.random()).toBeLessThanOrEqual(1);
|
|
198
|
+
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('initializes RNG only once', () => {
|
|
202
|
+
const seed = 'initial-seed';
|
|
203
|
+
initChance(seed);
|
|
204
|
+
const chance1 = getChance();
|
|
205
|
+
initChance('new-seed');
|
|
206
|
+
const chance2 = getChance();
|
|
207
|
+
expect(chance1).toBe(chance2);
|
|
208
|
+
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
describe('generation', () => {
|
|
214
|
+
test('users: can make', () => {
|
|
215
|
+
const numDays = 30;
|
|
216
|
+
const user = generateUser('uuid-123', numDays);
|
|
217
|
+
expect(user).toHaveProperty('distinct_id');
|
|
218
|
+
expect(user).toHaveProperty('name');
|
|
219
|
+
expect(user).toHaveProperty('email');
|
|
220
|
+
expect(user).toHaveProperty('avatar');
|
|
221
|
+
expect(user).toHaveProperty('created');
|
|
222
|
+
expect(user).toHaveProperty('anonymousIds');
|
|
223
|
+
expect(user).toHaveProperty('sessionIds');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('user: in time range', () => {
|
|
227
|
+
const numDays = 30;
|
|
228
|
+
const user = generateUser('uuid-123', numDays);
|
|
229
|
+
const createdDate = dayjs(user.created, 'YYYY-MM-DD');
|
|
230
|
+
expect(createdDate.isValid()).toBeTruthy();
|
|
231
|
+
expect(createdDate.isBefore(dayjs.unix(global.NOW))).toBeTruthy();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('validation', () => {
|
|
236
|
+
|
|
237
|
+
beforeAll(() => {
|
|
238
|
+
global.NOW = 1672531200; // fixed point in time for testing
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('events: throws non array', () => {
|
|
242
|
+
// @ts-ignore
|
|
243
|
+
expect(() => validateEventConfig("not an array")).toThrow("events must be an array");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('events: strings', () => {
|
|
247
|
+
const events = ["event1", "event2"];
|
|
248
|
+
const result = validateEventConfig(events);
|
|
249
|
+
|
|
250
|
+
expect(result).toEqual([
|
|
251
|
+
{ event: "event1", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
252
|
+
{ event: "event2", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
result.forEach(event => {
|
|
256
|
+
expect(event.weight).toBeGreaterThanOrEqual(1);
|
|
257
|
+
expect(event.weight).toBeLessThanOrEqual(5);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('events: objects', () => {
|
|
262
|
+
const events = [{ event: "event1", properties: { a: 1 } }, { event: "event2", properties: { b: 2 } }];
|
|
263
|
+
const result = validateEventConfig(events);
|
|
264
|
+
|
|
265
|
+
expect(result).toEqual(events);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('events: mix', () => {
|
|
269
|
+
const events = ["event1", { event: "event2", properties: { b: 2 } }];
|
|
270
|
+
// @ts-ignore
|
|
271
|
+
const result = validateEventConfig(events);
|
|
272
|
+
|
|
273
|
+
expect(result).toEqual([
|
|
274
|
+
{ event: "event1", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
275
|
+
{ event: "event2", properties: { b: 2 } }
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
expect(result[0].weight).toBeGreaterThanOrEqual(1);
|
|
279
|
+
expect(result[0].weight).toBeLessThanOrEqual(5);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('dates: between', () => {
|
|
283
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 15); // 15 days ago
|
|
284
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
285
|
+
const latestTime = global.NOW;
|
|
286
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('dates: outside earliest', () => {
|
|
290
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 31); // 31 days ago
|
|
291
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
292
|
+
const latestTime = global.NOW;
|
|
293
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(false);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('dates: outside latest', () => {
|
|
297
|
+
const chosenTime = -1;
|
|
298
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
299
|
+
const latestTime = global.NOW;
|
|
300
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(false);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('dates: inference in', () => {
|
|
304
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 15); // 15 days ago
|
|
305
|
+
expect(validateTime(chosenTime)).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('dates: inference out', () => {
|
|
309
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 31); // 31 days ago
|
|
310
|
+
expect(validateTime(chosenTime)).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('enrichment', () => {
|
|
315
|
+
test('hook works', () => {
|
|
316
|
+
const arr = [];
|
|
317
|
+
const hook = (item) => item * 2;
|
|
318
|
+
const enrichedArray = enrichArray(arr, { hook });
|
|
319
|
+
enrichedArray.hookPush(1);
|
|
320
|
+
enrichedArray.hookPush(2);
|
|
321
|
+
expect(enrichedArray.includes(2)).toBeTruthy();
|
|
322
|
+
expect(enrichedArray.includes(4)).toBeTruthy();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('filter empties', () => {
|
|
326
|
+
const arr = [];
|
|
327
|
+
const hook = (item) => item ? item.toString() : item;
|
|
328
|
+
const enrichedArray = enrichArray(arr, { hook });
|
|
329
|
+
enrichedArray.hookPush(null);
|
|
330
|
+
enrichedArray.hookPush(undefined);
|
|
331
|
+
enrichedArray.hookPush({});
|
|
332
|
+
enrichedArray.hookPush({ a: 1 });
|
|
333
|
+
enrichedArray.hookPush([1, 2]);
|
|
334
|
+
expect(enrichedArray).toHaveLength(3);
|
|
335
|
+
expect(enrichedArray.includes('null')).toBeFalsy();
|
|
336
|
+
expect(enrichedArray.includes('undefined')).toBeFalsy();
|
|
337
|
+
expect(enrichedArray.includes('[object Object]')).toBeTruthy();
|
|
338
|
+
expect(enrichedArray.includes('1')).toBeTruthy();
|
|
339
|
+
expect(enrichedArray.includes('2')).toBeTruthy();
|
|
340
|
+
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
});
|
|
27
345
|
|
|
28
|
-
const { applySkew, boxMullerRandom, choose, date, dates, day, exhaust, generateEmoji, getUniqueKeys, integer, mapToRange, person, pick, range, weighList, weightedRange } = require('../utils');
|
|
29
346
|
|
|
30
|
-
describe('
|
|
347
|
+
describe('utilities', () => {
|
|
31
348
|
|
|
32
349
|
test('pick: works', () => {
|
|
33
350
|
const array = [1, 2, 3];
|
|
@@ -58,9 +375,9 @@ describe('utils', () => {
|
|
|
58
375
|
|
|
59
376
|
test('person: fields', () => {
|
|
60
377
|
const generatedPerson = person();
|
|
61
|
-
expect(generatedPerson).toHaveProperty('
|
|
62
|
-
expect(generatedPerson).toHaveProperty('
|
|
63
|
-
expect(generatedPerson).toHaveProperty('
|
|
378
|
+
expect(generatedPerson).toHaveProperty('name');
|
|
379
|
+
expect(generatedPerson).toHaveProperty('email');
|
|
380
|
+
expect(generatedPerson).toHaveProperty('avatar');
|
|
64
381
|
});
|
|
65
382
|
|
|
66
383
|
|
|
@@ -73,7 +390,7 @@ describe('utils', () => {
|
|
|
73
390
|
test('date: future', () => {
|
|
74
391
|
const futureDate = date(10, false, 'YYYY-MM-DD')();
|
|
75
392
|
expect(dayjs(futureDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
|
|
76
|
-
expect(dayjs(futureDate).isAfter(dayjs())).toBeTruthy();
|
|
393
|
+
expect(dayjs(futureDate).isAfter(dayjs.unix(global.NOW))).toBeTruthy();
|
|
77
394
|
});
|
|
78
395
|
|
|
79
396
|
test('dates: pairs', () => {
|
|
@@ -107,15 +424,15 @@ describe('utils', () => {
|
|
|
107
424
|
});
|
|
108
425
|
|
|
109
426
|
test('weightedRange: within range', () => {
|
|
110
|
-
const values = weightedRange(5, 15
|
|
427
|
+
const values = weightedRange(5, 15);
|
|
111
428
|
expect(values.every(v => v >= 5 && v <= 15)).toBe(true);
|
|
112
|
-
expect(values.length).toBe(
|
|
429
|
+
expect(values.length).toBe(50);
|
|
113
430
|
});
|
|
114
431
|
|
|
115
432
|
test('applySkew: skews', () => {
|
|
116
433
|
const value = boxMullerRandom();
|
|
117
434
|
const skewedValue = applySkew(value, .25);
|
|
118
|
-
expect(Math.abs(skewedValue)).
|
|
435
|
+
expect(Math.abs(skewedValue)).toBeLessThanOrEqual(Math.abs(value));
|
|
119
436
|
});
|
|
120
437
|
|
|
121
438
|
test('mapToRange: works', () => {
|
|
@@ -146,6 +463,19 @@ describe('utils', () => {
|
|
|
146
463
|
});
|
|
147
464
|
|
|
148
465
|
|
|
466
|
+
test('times', () => {
|
|
467
|
+
const dates = [];
|
|
468
|
+
for (let i = 0; i < 10000; i++) {
|
|
469
|
+
const earliest = dayjs().subtract(u.rand(5, 50), 'D');
|
|
470
|
+
dates.push(TimeSoup());
|
|
471
|
+
}
|
|
472
|
+
const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
|
|
473
|
+
const badYear = dates.filter(d => !d.startsWith('202'));
|
|
474
|
+
expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
|
|
475
|
+
expect(dates.every(d => d.startsWith('202'))).toBe(true);
|
|
476
|
+
|
|
477
|
+
});
|
|
478
|
+
|
|
149
479
|
test('date', () => {
|
|
150
480
|
const result = date();
|
|
151
481
|
expect(dayjs(result()).isValid()).toBe(true);
|
|
@@ -206,4 +536,101 @@ describe('utils', () => {
|
|
|
206
536
|
});
|
|
207
537
|
|
|
208
538
|
|
|
539
|
+
test('weighArray: works', () => {
|
|
540
|
+
const arr = ['a', 'b', 'c'];
|
|
541
|
+
const weightedArr = weighArray(arr);
|
|
542
|
+
expect(weightedArr.length).toBeGreaterThanOrEqual(arr.length);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test('weighFunnels: works', () => {
|
|
546
|
+
const acc = [];
|
|
547
|
+
const funnel = { weight: 3 };
|
|
548
|
+
const result = weighFunnels(acc, funnel);
|
|
549
|
+
expect(result.length).toBe(3);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('progress: outputs correctly', () => {
|
|
553
|
+
// @ts-ignore
|
|
554
|
+
const mockStdoutWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
|
|
555
|
+
progress('test', 50);
|
|
556
|
+
expect(mockStdoutWrite).toHaveBeenCalled();
|
|
557
|
+
mockStdoutWrite.mockRestore();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test('range: works', () => {
|
|
561
|
+
const result = [];
|
|
562
|
+
range.call(result, 1, 5);
|
|
563
|
+
expect(result).toEqual([1, 2, 3, 4, 5]);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
test('shuffleArray: works', () => {
|
|
569
|
+
const arr = [1, 2, 3, 4, 5];
|
|
570
|
+
const shuffled = shuffleArray([...arr]);
|
|
571
|
+
expect(shuffled).not.toEqual(arr);
|
|
572
|
+
expect(shuffled.sort()).toEqual(arr.sort());
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test('shuffleExceptFirst: works', () => {
|
|
576
|
+
const arr = [1, 2, 3, 4, 5];
|
|
577
|
+
const shuffled = shuffleExceptFirst([...arr]);
|
|
578
|
+
expect(shuffled[0]).toBe(arr[0]);
|
|
579
|
+
expect(shuffled.slice(1).sort()).toEqual(arr.slice(1).sort());
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('shuffleExceptLast: works', () => {
|
|
583
|
+
const arr = [1, 2, 3, 4, 5];
|
|
584
|
+
const shuffled = shuffleExceptLast([...arr]);
|
|
585
|
+
expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
|
|
586
|
+
expect(shuffled.slice(0, -1).sort()).toEqual(arr.slice(0, -1).sort());
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test('fixFirstAndLast: works', () => {
|
|
590
|
+
const arr = [1, 2, 3, 4, 5];
|
|
591
|
+
const shuffled = fixFirstAndLast([...arr]);
|
|
592
|
+
expect(shuffled[0]).toBe(arr[0]);
|
|
593
|
+
expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
|
|
594
|
+
expect(shuffled.slice(1, -1).sort()).toEqual(arr.slice(1, -1).sort());
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test('shuffleMiddle: works', () => {
|
|
598
|
+
const arr = [1, 2, 3, 4, 5];
|
|
599
|
+
const shuffled = shuffleMiddle([...arr]);
|
|
600
|
+
expect(shuffled[0]).toBe(arr[0]);
|
|
601
|
+
expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
|
|
602
|
+
expect(shuffled.slice(1, -1).sort()).toEqual(arr.slice(1, -1).sort());
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test('shuffleOutside: works', () => {
|
|
606
|
+
const arr = [1, 2, 3, 4, 5];
|
|
607
|
+
const shuffled = shuffleOutside([...arr]);
|
|
608
|
+
expect(shuffled.slice(1, -1)).toEqual(arr.slice(1, -1));
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test('box normal distribution', () => {
|
|
612
|
+
const values = [];
|
|
613
|
+
for (let i = 0; i < 10000; i++) {
|
|
614
|
+
values.push(boxMullerRandom());
|
|
615
|
+
}
|
|
616
|
+
const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
617
|
+
const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
|
|
618
|
+
const stdDev = Math.sqrt(variance);
|
|
619
|
+
expect(mean).toBeCloseTo(0, 1);
|
|
620
|
+
expect(stdDev).toBeCloseTo(1, 1);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test('optimized box normal distribution', () => {
|
|
624
|
+
const values = [];
|
|
625
|
+
for (let i = 0; i < 10000; i++) {
|
|
626
|
+
values.push(optimizedBoxMuller());
|
|
627
|
+
}
|
|
628
|
+
const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
629
|
+
const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
|
|
630
|
+
const stdDev = Math.sqrt(variance);
|
|
631
|
+
expect(mean).toBeCloseTo(0, 1);
|
|
632
|
+
expect(stdDev).toBeCloseTo(1, 1);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
|
|
209
636
|
});
|
package/tmp/.gitkeep
ADDED
|
File without changes
|
package/tsconfig.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -9,16 +9,20 @@ declare namespace main {
|
|
|
9
9
|
token?: string;
|
|
10
10
|
seed?: string;
|
|
11
11
|
numDays?: number;
|
|
12
|
+
epochStart?: number;
|
|
13
|
+
epochEnd?: number;
|
|
12
14
|
numEvents?: number;
|
|
13
15
|
numUsers?: number;
|
|
14
16
|
format?: "csv" | "json";
|
|
15
17
|
region?: "US" | "EU";
|
|
16
|
-
|
|
18
|
+
chance?: any;
|
|
19
|
+
events?: EventConfig[]; //can also be a array of strings
|
|
17
20
|
superProps?: Record<string, ValueValid>;
|
|
21
|
+
funnels?: Funnel[];
|
|
18
22
|
userProps?: Record<string, ValueValid>;
|
|
19
23
|
scdProps?: Record<string, ValueValid>;
|
|
20
24
|
mirrorProps?: Record<string, MirrorProps>;
|
|
21
|
-
groupKeys?: [string, number][] | [string, number, string[]][];
|
|
25
|
+
groupKeys?: [string, number][] | [string, number, string[]][]; // [key, numGroups, [events]]
|
|
22
26
|
groupProps?: Record<string, Record<string, ValueValid>>;
|
|
23
27
|
lookupTables?: LookupTable[];
|
|
24
28
|
writeToDisk?: boolean;
|
|
@@ -26,19 +30,37 @@ declare namespace main {
|
|
|
26
30
|
verbose?: boolean;
|
|
27
31
|
anonIds?: boolean;
|
|
28
32
|
sessionIds?: boolean;
|
|
33
|
+
makeChart?: boolean | string;
|
|
34
|
+
soup?: soup;
|
|
29
35
|
hook?: Hook<any>;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
type soup = {
|
|
39
|
+
deviation?: number;
|
|
40
|
+
peaks?: number;
|
|
41
|
+
mean?: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type hookTypes =
|
|
45
|
+
| "event"
|
|
46
|
+
| "user"
|
|
47
|
+
| "group"
|
|
48
|
+
| "lookup"
|
|
49
|
+
| "scd"
|
|
50
|
+
| "mirror"
|
|
51
|
+
| "funnel-pre"
|
|
52
|
+
| "funnel-post"
|
|
53
|
+
| "";
|
|
54
|
+
export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
|
|
33
55
|
|
|
34
56
|
export interface EnrichArrayOptions<T> {
|
|
35
57
|
hook?: Hook<T>;
|
|
36
|
-
type?:
|
|
58
|
+
type?: hookTypes;
|
|
37
59
|
[key: string]: any;
|
|
38
60
|
}
|
|
39
61
|
|
|
40
62
|
export interface EnrichedArray<T> extends Array<T> {
|
|
41
|
-
|
|
63
|
+
hookPush: (item: T) => boolean;
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
export interface EventConfig {
|
|
@@ -46,6 +68,40 @@ declare namespace main {
|
|
|
46
68
|
weight?: number;
|
|
47
69
|
properties?: Record<string, ValueValid>;
|
|
48
70
|
isFirstEvent?: boolean;
|
|
71
|
+
relativeTimeMs?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface EventSpec {
|
|
75
|
+
event: string;
|
|
76
|
+
time: string;
|
|
77
|
+
insert_id: string;
|
|
78
|
+
device_id?: string;
|
|
79
|
+
session_id?: string;
|
|
80
|
+
user_id?: string;
|
|
81
|
+
[key: string]: ValueValid;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface Funnel {
|
|
85
|
+
sequence: string[];
|
|
86
|
+
weight?: number;
|
|
87
|
+
isFirstFunnel?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* If true, the funnel will require the user to repeat the sequence of events in order to convert
|
|
90
|
+
* If false, the user does not need to repeat the sequence of events in order to convert
|
|
91
|
+
* ^ when false, users who repeat the repetitive steps are more likely to convert
|
|
92
|
+
*/
|
|
93
|
+
requireRepeats?: boolean;
|
|
94
|
+
order?:
|
|
95
|
+
| "sequential"
|
|
96
|
+
| "first-fixed"
|
|
97
|
+
| "last-fixed"
|
|
98
|
+
| "random"
|
|
99
|
+
| "first-and-last-fixed"
|
|
100
|
+
| "middle-fixed"
|
|
101
|
+
| "interrupted";
|
|
102
|
+
conversionRate?: number;
|
|
103
|
+
timeToConvert?: number;
|
|
104
|
+
props?: Record<string, ValueValid>;
|
|
49
105
|
}
|
|
50
106
|
|
|
51
107
|
export interface MirrorProps {
|
|
@@ -59,7 +115,7 @@ declare namespace main {
|
|
|
59
115
|
attributes: Record<string, ValueValid>;
|
|
60
116
|
}
|
|
61
117
|
|
|
62
|
-
export interface
|
|
118
|
+
export interface SCDTableRow {
|
|
63
119
|
distinct_id: string;
|
|
64
120
|
insertTime: string;
|
|
65
121
|
startTime: string;
|
|
@@ -72,17 +128,17 @@ declare namespace main {
|
|
|
72
128
|
scdTableData: any[];
|
|
73
129
|
groupProfilesData: GroupProfilesData[];
|
|
74
130
|
lookupTableData: LookupTableData[];
|
|
75
|
-
|
|
131
|
+
importResults?: ImportResults;
|
|
76
132
|
files?: string[];
|
|
77
133
|
};
|
|
78
134
|
|
|
79
135
|
export interface EventData {
|
|
80
136
|
event: string;
|
|
81
|
-
|
|
137
|
+
source: string;
|
|
82
138
|
time: string;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
139
|
+
device_id?: string;
|
|
140
|
+
session_id?: string;
|
|
141
|
+
user_id?: string;
|
|
86
142
|
[key: string]: any;
|
|
87
143
|
}
|
|
88
144
|
|
|
@@ -107,12 +163,22 @@ declare namespace main {
|
|
|
107
163
|
bytes: number;
|
|
108
164
|
}
|
|
109
165
|
export interface Person {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
166
|
+
name: string;
|
|
167
|
+
email: string;
|
|
168
|
+
avatar: string;
|
|
169
|
+
created: string | undefined;
|
|
114
170
|
anonymousIds: string[];
|
|
115
171
|
sessionIds: string[];
|
|
172
|
+
distinct_id?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface UserProfile {
|
|
176
|
+
name?: string;
|
|
177
|
+
email?: string;
|
|
178
|
+
avatar?: string;
|
|
179
|
+
created: string | undefined;
|
|
180
|
+
distinct_id: string;
|
|
181
|
+
[key: string]: ValueValid;
|
|
116
182
|
}
|
|
117
183
|
}
|
|
118
184
|
|