make-mp-data 1.5.55 → 2.0.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/.claude/settings.local.json +20 -0
- package/.gcloudignore +2 -1
- package/.vscode/launch.json +6 -3
- package/.vscode/settings.json +31 -2
- package/dungeons/media.js +371 -0
- package/index.js +354 -1757
- package/{components → lib/cli}/cli.js +21 -6
- package/lib/cloud-function.js +20 -0
- package/lib/core/config-validator.js +248 -0
- package/lib/core/context.js +180 -0
- package/lib/core/storage.js +268 -0
- package/{components → lib/data}/defaults.js +17 -14
- package/lib/generators/adspend.js +133 -0
- package/lib/generators/events.js +242 -0
- package/lib/generators/funnels.js +330 -0
- package/lib/generators/mirror.js +168 -0
- package/lib/generators/profiles.js +93 -0
- package/lib/generators/scd.js +102 -0
- package/lib/orchestrators/mixpanel-sender.js +222 -0
- package/lib/orchestrators/user-loop.js +194 -0
- package/lib/orchestrators/worker-manager.js +200 -0
- package/{components → lib/utils}/ai.js +8 -36
- package/{components → lib/utils}/chart.js +9 -9
- package/{components → lib/utils}/project.js +4 -4
- package/{components → lib/utils}/utils.js +35 -23
- package/package.json +19 -12
- package/scripts/dana.mjs +137 -0
- package/scripts/new-dungeon.sh +7 -6
- package/scripts/update-deps.sh +2 -1
- package/tests/cli.test.js +28 -25
- package/tests/e2e.test.js +38 -36
- package/tests/int.test.js +151 -56
- package/tests/testSoup.mjs +1 -1
- package/tests/unit.test.js +15 -14
- package/tsconfig.json +1 -1
- package/types.d.ts +76 -18
- package/vitest.config.js +47 -0
- package/dungeons/adspend.js +0 -96
- package/dungeons/anon.js +0 -104
- package/dungeons/big.js +0 -224
- package/dungeons/business.js +0 -327
- package/dungeons/complex.js +0 -396
- package/dungeons/foobar.js +0 -241
- package/dungeons/funnels.js +0 -220
- package/dungeons/gaming-experiments.js +0 -323
- package/dungeons/gaming.js +0 -314
- package/dungeons/governance.js +0 -288
- package/dungeons/mirror.js +0 -129
- package/dungeons/sanity.js +0 -118
- package/dungeons/scd.js +0 -205
- package/dungeons/session-replay.js +0 -175
- package/dungeons/simple.js +0 -150
- package/dungeons/userAgent.js +0 -190
- package/log.json +0 -1067
- package/tests/jest.config.js +0 -47
- /package/{components → lib/utils}/prompt.txt +0 -0
package/tests/int.test.js
CHANGED
|
@@ -1,29 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import utc from "dayjs/plugin/utc.js";
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import * as u from 'ak-tools';
|
|
5
5
|
dayjs.extend(utc);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/** @typedef {import('../types
|
|
10
|
-
/** @typedef {import('../types
|
|
11
|
-
/** @typedef {import("../types
|
|
12
|
-
/** @typedef {import('../types
|
|
13
|
-
/** @typedef {import('../types
|
|
14
|
-
/** @typedef {import('../types
|
|
15
|
-
/** @typedef {import('../types
|
|
16
|
-
/** @typedef {import('../types
|
|
17
|
-
/** @typedef {import('../types
|
|
18
|
-
/** @typedef {import('../types
|
|
19
|
-
/** @typedef {import('../types
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
6
|
+
import 'dotenv/config';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('../types').Dungeon} Config */
|
|
10
|
+
/** @typedef {import('../types').EventConfig} EventConfig */
|
|
11
|
+
/** @typedef {import("../types").EventSchema} EventSchema */
|
|
12
|
+
/** @typedef {import('../types').ValueValid} ValueValid */
|
|
13
|
+
/** @typedef {import('../types').HookedArray} hookArray */
|
|
14
|
+
/** @typedef {import('../types').hookArrayOptions} hookArrayOptions */
|
|
15
|
+
/** @typedef {import('../types').Person} Person */
|
|
16
|
+
/** @typedef {import('../types').Funnel} Funnel */
|
|
17
|
+
/** @typedef {import('../types').UserProfile} UserProfile */
|
|
18
|
+
/** @typedef {import('../types').SCDSchema} SCDSchema */
|
|
19
|
+
/** @typedef {import('../types').Storage} Storage */
|
|
20
|
+
|
|
21
|
+
// Import main function
|
|
22
|
+
import main from '../index.js';
|
|
23
|
+
|
|
24
|
+
// Import generators directly
|
|
25
|
+
import { makeAdSpend } from '../lib/generators/adspend.js';
|
|
26
|
+
import { makeEvent } from '../lib/generators/events.js';
|
|
27
|
+
import { makeFunnel } from '../lib/generators/funnels.js';
|
|
28
|
+
import { makeProfile } from '../lib/generators/profiles.js';
|
|
29
|
+
import { makeSCD } from '../lib/generators/scd.js';
|
|
30
|
+
import { makeMirror } from '../lib/generators/mirror.js';
|
|
31
|
+
|
|
32
|
+
// Import orchestrators directly
|
|
33
|
+
import { sendToMixpanel } from '../lib/orchestrators/mixpanel-sender.js';
|
|
34
|
+
import { userLoop } from '../lib/orchestrators/user-loop.js';
|
|
35
|
+
import { validateDungeonConfig } from '../lib/core/config-validator.js';
|
|
36
|
+
|
|
37
|
+
// Import utilities directly
|
|
38
|
+
import { createHookArray } from '../lib/core/storage.js';
|
|
39
|
+
import { inferFunnels } from '../lib/core/config-validator.js';
|
|
40
|
+
import { validEvent } from '../lib/utils/utils.js';
|
|
41
|
+
import { createContext } from '../lib/core/context.js';
|
|
42
|
+
import { validateDungeonConfig } from '../lib/core/config-validator.js';
|
|
43
|
+
|
|
44
|
+
// Alias for compatibility
|
|
45
|
+
const hookArray = createHookArray;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a test context object for generator function testing
|
|
49
|
+
* @param {Object} configOverrides - Config overrides for testing
|
|
50
|
+
* @returns {Object} Test context object
|
|
51
|
+
*/
|
|
52
|
+
function createTestContext(configOverrides = {}) {
|
|
53
|
+
const baseConfig = {
|
|
54
|
+
numEvents: 100,
|
|
55
|
+
numUsers: 10,
|
|
56
|
+
numDays: 30,
|
|
57
|
+
hasAdSpend: false,
|
|
58
|
+
hasLocation: false,
|
|
59
|
+
hasAvatar: false,
|
|
60
|
+
verbose: false,
|
|
61
|
+
writeToDisk: false,
|
|
62
|
+
isAnonymous: false,
|
|
63
|
+
hasAnonIds: false,
|
|
64
|
+
hasSessionIds: false,
|
|
65
|
+
concurrency: 1,
|
|
66
|
+
...configOverrides
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const validatedConfig = validateDungeonConfig(baseConfig);
|
|
70
|
+
const context = createContext(validatedConfig);
|
|
71
|
+
|
|
72
|
+
return context;
|
|
73
|
+
}
|
|
27
74
|
|
|
28
75
|
|
|
29
76
|
// Mock the global variables
|
|
@@ -31,7 +78,7 @@ let CAMPAIGNS;
|
|
|
31
78
|
let DEFAULTS;
|
|
32
79
|
let STORAGE;
|
|
33
80
|
let CONFIG;
|
|
34
|
-
|
|
81
|
+
import { campaigns, devices, locations } from '../lib/data/defaults.js';
|
|
35
82
|
|
|
36
83
|
beforeEach(async () => {
|
|
37
84
|
// Reset global variables before each test
|
|
@@ -81,7 +128,7 @@ beforeEach(() => {
|
|
|
81
128
|
|
|
82
129
|
});
|
|
83
130
|
|
|
84
|
-
describe('generators', () => {
|
|
131
|
+
describe.sequential('generators', () => {
|
|
85
132
|
|
|
86
133
|
test('adspend: works', async () => {
|
|
87
134
|
const campaigns = [{
|
|
@@ -98,14 +145,16 @@ describe('generators', () => {
|
|
|
98
145
|
utm_content: ["seven"],
|
|
99
146
|
utm_term: ["eight"]
|
|
100
147
|
}];
|
|
101
|
-
const
|
|
148
|
+
const context = createTestContext({ hasAdSpend: true });
|
|
149
|
+
const result = await makeAdSpend(context, dayjs().subtract(30, 'day').toISOString(), campaigns);
|
|
102
150
|
expect(result.length).toBe(2);
|
|
103
151
|
expect(result[0]).toHaveProperty('event', '$ad_spend');
|
|
104
152
|
expect(result[1]).toHaveProperty('event', '$ad_spend');
|
|
105
153
|
});
|
|
106
154
|
|
|
107
155
|
test('adspend: empty', async () => {
|
|
108
|
-
const
|
|
156
|
+
const context = createTestContext({ hasAdSpend: true });
|
|
157
|
+
const result = await makeAdSpend(context, dayjs().subtract(30, 'day').toISOString(), []);
|
|
109
158
|
expect(result.length).toBe(0);
|
|
110
159
|
});
|
|
111
160
|
|
|
@@ -114,7 +163,8 @@ describe('generators', () => {
|
|
|
114
163
|
{ utm_source: ["source1"], utm_campaign: ["one"], utm_medium: ["two"], utm_content: ["three"], utm_term: ["four"] },
|
|
115
164
|
{ utm_source: ["source2"], utm_campaign: ["two"], utm_medium: ["three"], utm_content: ["four"], utm_term: ["five"] }
|
|
116
165
|
];
|
|
117
|
-
const
|
|
166
|
+
const context = createTestContext({ hasAdSpend: true });
|
|
167
|
+
const result = await makeAdSpend(context, dayjs().subtract(30, 'day').toISOString(), campaigns);
|
|
118
168
|
expect(result.length).toBe(2);
|
|
119
169
|
result.forEach(event => {
|
|
120
170
|
expect(event).toHaveProperty('event', '$ad_spend');
|
|
@@ -138,7 +188,8 @@ describe('generators', () => {
|
|
|
138
188
|
prop3: ["value5"]
|
|
139
189
|
},
|
|
140
190
|
};
|
|
141
|
-
const
|
|
191
|
+
const context = createTestContext();
|
|
192
|
+
const result = await makeEvent(context, "known_id", dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), eventConfig, ["anon_id"], ["session_id"]);
|
|
142
193
|
expect(result).toHaveProperty('event', 'test_event');
|
|
143
194
|
expect(result).toHaveProperty('device_id', 'anon_id');
|
|
144
195
|
// expect(result).toHaveProperty('user_id', 'known_id'); // Known ID not always on the event
|
|
@@ -155,7 +206,8 @@ describe('generators', () => {
|
|
|
155
206
|
|
|
156
207
|
test('makeEvent: opt params', async () => {
|
|
157
208
|
const eventConfig = { event: "test_event", properties: {} };
|
|
158
|
-
const
|
|
209
|
+
const context = createTestContext();
|
|
210
|
+
const result = await makeEvent(context, "known_id", dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), eventConfig);
|
|
159
211
|
expect(result).toHaveProperty('event', 'test_event');
|
|
160
212
|
expect(result).toHaveProperty('user_id', 'known_id');
|
|
161
213
|
expect(result).toHaveProperty('source', 'dm4');
|
|
@@ -171,7 +223,8 @@ describe('generators', () => {
|
|
|
171
223
|
prop2: ["value3", "value4"]
|
|
172
224
|
},
|
|
173
225
|
};
|
|
174
|
-
const
|
|
226
|
+
const context = createTestContext();
|
|
227
|
+
const result = await makeEvent(context, "known_id", dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), eventConfig, ["anon_id"], ["session_id"]);
|
|
175
228
|
expect(result.prop1 === "value1" || result.prop1 === "value2").toBeTruthy();
|
|
176
229
|
expect(result.prop2 === "value3" || result.prop2 === "value4").toBeTruthy();
|
|
177
230
|
});
|
|
@@ -190,7 +243,8 @@ describe('generators', () => {
|
|
|
190
243
|
/** @type {Record<string, SCDSchema[]>} */
|
|
191
244
|
const scd = { "scd_example": [{ distinct_id: "user1", insertTime: dayjs().toISOString(), startTime: dayjs().toISOString() }] };
|
|
192
245
|
|
|
193
|
-
const
|
|
246
|
+
const context = createTestContext();
|
|
247
|
+
const [result, converted] = await makeFunnel(context, funnelConfig, user, dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), profile, scd);
|
|
194
248
|
expect(result.length).toBe(2);
|
|
195
249
|
expect(converted).toBe(true);
|
|
196
250
|
expect(result.every(e => validEvent(e))).toBeTruthy();
|
|
@@ -206,7 +260,8 @@ describe('generators', () => {
|
|
|
206
260
|
const profile = { created: dayjs().toISOString(), distinct_id: "user1" };
|
|
207
261
|
const scd = { "scd_example": [{ distinct_id: "user1", insertTime: dayjs().toISOString(), startTime: dayjs().toISOString() }] };
|
|
208
262
|
|
|
209
|
-
const
|
|
263
|
+
const context = createTestContext();
|
|
264
|
+
const [result, converted] = await makeFunnel(context, funnelConfig, user, dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), profile, scd);
|
|
210
265
|
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
211
266
|
expect(result.length).toBeLessThanOrEqual(3);
|
|
212
267
|
expect(result.every(e => validEvent(e))).toBeTruthy();
|
|
@@ -222,7 +277,8 @@ describe('generators', () => {
|
|
|
222
277
|
const profile = { created: dayjs().toISOString(), distinct_id: "user1" };
|
|
223
278
|
const scd = { "scd_example": [{ distinct_id: "user1", insertTime: dayjs().toISOString(), startTime: dayjs().toISOString() }] };
|
|
224
279
|
|
|
225
|
-
const
|
|
280
|
+
const context = createTestContext();
|
|
281
|
+
const [result, converted] = await makeFunnel(context, funnelConfig, user, dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), profile, scd);
|
|
226
282
|
expect(result.length).toBe(3);
|
|
227
283
|
expect(converted).toBe(true);
|
|
228
284
|
expect(result.every(e => validEvent(e))).toBeTruthy();
|
|
@@ -230,11 +286,12 @@ describe('generators', () => {
|
|
|
230
286
|
|
|
231
287
|
|
|
232
288
|
test('makeProfile: works', async () => {
|
|
289
|
+
const context = createTestContext();
|
|
233
290
|
const props = {
|
|
234
291
|
name: ["John", "Jane"],
|
|
235
292
|
age: [25, 30]
|
|
236
293
|
};
|
|
237
|
-
const result = await makeProfile(props, { foo: "bar" });
|
|
294
|
+
const result = await makeProfile(context, props, { foo: "bar" });
|
|
238
295
|
expect(result).toHaveProperty('name');
|
|
239
296
|
expect(result).toHaveProperty('age');
|
|
240
297
|
expect(result).toHaveProperty('foo', 'bar');
|
|
@@ -243,11 +300,12 @@ describe('generators', () => {
|
|
|
243
300
|
});
|
|
244
301
|
|
|
245
302
|
test('makeProfile: correct defaults', async () => {
|
|
303
|
+
const context = createTestContext();
|
|
246
304
|
const props = {
|
|
247
305
|
name: ["John", "Jane"],
|
|
248
306
|
age: [25, 30]
|
|
249
307
|
};
|
|
250
|
-
const result = await makeProfile(props);
|
|
308
|
+
const result = await makeProfile(context, props);
|
|
251
309
|
expect(result).toHaveProperty('name');
|
|
252
310
|
expect(result).toHaveProperty('age');
|
|
253
311
|
expect(result.name === "John" || result.name === "Jane").toBeTruthy();
|
|
@@ -256,7 +314,8 @@ describe('generators', () => {
|
|
|
256
314
|
|
|
257
315
|
|
|
258
316
|
test('makeSCD: works', async () => {
|
|
259
|
-
const
|
|
317
|
+
const context = createTestContext();
|
|
318
|
+
const result = await makeSCD(context, ["value1", "value2"], "prop1", "distinct_id", 5, dayjs().toISOString());
|
|
260
319
|
expect(result.length).toBeGreaterThan(0);
|
|
261
320
|
const [first, second] = result;
|
|
262
321
|
expect(first).toHaveProperty('prop1');
|
|
@@ -275,12 +334,14 @@ describe('generators', () => {
|
|
|
275
334
|
});
|
|
276
335
|
|
|
277
336
|
test('makeSCD: no mutations', async () => {
|
|
278
|
-
const
|
|
337
|
+
const context = createTestContext();
|
|
338
|
+
const result = await makeSCD(context, ["value1", "value2"], "prop1", "distinct_id", 0, dayjs().toISOString());
|
|
279
339
|
expect(result.length).toBe(0);
|
|
280
340
|
});
|
|
281
341
|
|
|
282
342
|
test('makeSCD: large mutations', async () => {
|
|
283
|
-
const
|
|
343
|
+
const context = createTestContext();
|
|
344
|
+
const result = await makeSCD(context, ["value1", "value2"], "prop1", "distinct_id", 100, dayjs().subtract(100, 'd').toISOString());
|
|
284
345
|
expect(result.length).toBeGreaterThan(0);
|
|
285
346
|
result.forEach(entry => {
|
|
286
347
|
expect(entry).toHaveProperty('prop1');
|
|
@@ -313,7 +374,10 @@ describe('generators', () => {
|
|
|
313
374
|
};
|
|
314
375
|
await STORAGE.eventData.hookPush(oldEvent);
|
|
315
376
|
//ugh side fx
|
|
316
|
-
|
|
377
|
+
// Create context with the test config and storage
|
|
378
|
+
const context = createTestContext(config);
|
|
379
|
+
context.setStorage(STORAGE);
|
|
380
|
+
await makeMirror(context);
|
|
317
381
|
const [newData] = STORAGE.mirrorEventData;
|
|
318
382
|
expect(newData).toHaveProperty('newProp', "new");
|
|
319
383
|
});
|
|
@@ -340,7 +404,10 @@ describe('generators', () => {
|
|
|
340
404
|
};
|
|
341
405
|
await STORAGE.eventData.hookPush(oldEvent);
|
|
342
406
|
|
|
343
|
-
|
|
407
|
+
// Create context with the test config and storage
|
|
408
|
+
const context = createTestContext(config);
|
|
409
|
+
context.setStorage(STORAGE);
|
|
410
|
+
await makeMirror(context);
|
|
344
411
|
const [newData] = STORAGE.mirrorEventData;
|
|
345
412
|
expect(newData).not.toHaveProperty('oldProp');
|
|
346
413
|
});
|
|
@@ -369,7 +436,10 @@ describe('generators', () => {
|
|
|
369
436
|
};
|
|
370
437
|
await STORAGE.eventData.hookPush(oldEvent);
|
|
371
438
|
|
|
372
|
-
|
|
439
|
+
// Create context with the test config and storage
|
|
440
|
+
const context = createTestContext(config);
|
|
441
|
+
context.setStorage(STORAGE);
|
|
442
|
+
await makeMirror(context);
|
|
373
443
|
const [newData] = STORAGE.mirrorEventData;
|
|
374
444
|
expect(newData).toHaveProperty('fillProp', "filledValue");
|
|
375
445
|
});
|
|
@@ -397,7 +467,10 @@ describe('generators', () => {
|
|
|
397
467
|
};
|
|
398
468
|
await STORAGE.eventData.hookPush(oldEvent);
|
|
399
469
|
|
|
400
|
-
|
|
470
|
+
// Create context with the test config and storage
|
|
471
|
+
const context = createTestContext(config);
|
|
472
|
+
context.setStorage(STORAGE);
|
|
473
|
+
await makeMirror(context);
|
|
401
474
|
const [newData] = STORAGE.mirrorEventData;
|
|
402
475
|
expect(newData).toHaveProperty('updateProp', "initialValue");
|
|
403
476
|
});
|
|
@@ -425,7 +498,10 @@ describe('generators', () => {
|
|
|
425
498
|
};
|
|
426
499
|
await STORAGE.eventData.hookPush(oldEvent);
|
|
427
500
|
|
|
428
|
-
|
|
501
|
+
// Create context with the test config and storage
|
|
502
|
+
const context = createTestContext(config);
|
|
503
|
+
context.setStorage(STORAGE);
|
|
504
|
+
await makeMirror(context);
|
|
429
505
|
const [newData] = STORAGE.mirrorEventData;
|
|
430
506
|
expect(newData).toHaveProperty('updateProp', "updatedValue");
|
|
431
507
|
});
|
|
@@ -454,7 +530,10 @@ describe('generators', () => {
|
|
|
454
530
|
};
|
|
455
531
|
await STORAGE.eventData.hookPush(oldEvent);
|
|
456
532
|
|
|
457
|
-
|
|
533
|
+
// Create context with the test config and storage
|
|
534
|
+
const context = createTestContext(config);
|
|
535
|
+
context.setStorage(STORAGE);
|
|
536
|
+
await makeMirror(context);
|
|
458
537
|
const [newData] = STORAGE.mirrorEventData;
|
|
459
538
|
expect(newData).toHaveProperty('updateProp', "updatedValue");
|
|
460
539
|
});
|
|
@@ -463,11 +542,13 @@ describe('generators', () => {
|
|
|
463
542
|
|
|
464
543
|
});
|
|
465
544
|
|
|
466
|
-
describe('orchestrators', () => {
|
|
545
|
+
describe.sequential('orchestrators', () => {
|
|
467
546
|
|
|
468
547
|
test('sendToMixpanel: works', async () => {
|
|
469
548
|
CONFIG.token = "test_token";
|
|
470
|
-
const
|
|
549
|
+
const context = createTestContext(CONFIG);
|
|
550
|
+
context.setStorage(STORAGE);
|
|
551
|
+
const result = await sendToMixpanel(context);
|
|
471
552
|
expect(result).toHaveProperty('events');
|
|
472
553
|
expect(result).toHaveProperty('users');
|
|
473
554
|
expect(result).toHaveProperty('groups');
|
|
@@ -475,7 +556,9 @@ describe('orchestrators', () => {
|
|
|
475
556
|
|
|
476
557
|
test('sendToMixpanel: no token', async () => {
|
|
477
558
|
CONFIG.token = null;
|
|
478
|
-
|
|
559
|
+
const context = createTestContext(CONFIG);
|
|
560
|
+
context.setStorage(STORAGE);
|
|
561
|
+
await expect(sendToMixpanel(context)).rejects.toThrow();
|
|
479
562
|
});
|
|
480
563
|
|
|
481
564
|
test('sendToMixpanel: empty storage', async () => {
|
|
@@ -489,7 +572,9 @@ describe('orchestrators', () => {
|
|
|
489
572
|
lookupTableData: await hookArray([], {}),
|
|
490
573
|
mirrorEventData: await hookArray([], {})
|
|
491
574
|
};
|
|
492
|
-
const
|
|
575
|
+
const context = createTestContext(CONFIG);
|
|
576
|
+
context.setStorage(STORAGE);
|
|
577
|
+
const result = await sendToMixpanel(context);
|
|
493
578
|
expect(result.events.success).toBe(0);
|
|
494
579
|
expect(result.users.success).toBe(0);
|
|
495
580
|
expect(result.groups).toHaveLength(0);
|
|
@@ -512,7 +597,9 @@ describe('orchestrators', () => {
|
|
|
512
597
|
alsoInferFunnels: false,
|
|
513
598
|
events: [{ event: "foo" }, { event: "bar" }, { event: "baz" }]
|
|
514
599
|
};
|
|
515
|
-
|
|
600
|
+
const context = createTestContext(config);
|
|
601
|
+
context.setStorage(STORAGE);
|
|
602
|
+
await userLoop(context);
|
|
516
603
|
expect(STORAGE.userProfilesData.length).toBe(2);
|
|
517
604
|
expect(STORAGE.eventData.length).toBeGreaterThan(15);
|
|
518
605
|
expect(STORAGE.eventData.every(e => validEvent(e))).toBeTruthy();
|
|
@@ -530,7 +617,9 @@ describe('orchestrators', () => {
|
|
|
530
617
|
events: [],
|
|
531
618
|
funnels: [{ sequence: ["step1", "step2"], conversionRate: 100, order: 'sequential' }],
|
|
532
619
|
};
|
|
533
|
-
|
|
620
|
+
const context = createTestContext(config);
|
|
621
|
+
context.setStorage(STORAGE);
|
|
622
|
+
await userLoop(context);
|
|
534
623
|
expect(STORAGE.userProfilesData.length).toBe(2);
|
|
535
624
|
expect(STORAGE.eventData.length).toBeGreaterThan(15);
|
|
536
625
|
expect(STORAGE.eventData.every(e => validEvent(e))).toBeTruthy();
|
|
@@ -547,10 +636,14 @@ describe('orchestrators', () => {
|
|
|
547
636
|
scdProps: { prop1: ["value1", "value2"] },
|
|
548
637
|
funnels: [],
|
|
549
638
|
events: [{ event: "event1" }, { event: "event2" }]
|
|
639
|
+
|
|
550
640
|
};
|
|
551
|
-
|
|
641
|
+
const context = createTestContext(config);
|
|
642
|
+
context.setStorage(STORAGE);
|
|
643
|
+
await userLoop(context);
|
|
552
644
|
expect(STORAGE.userProfilesData.length).toBe(3);
|
|
553
|
-
expect(STORAGE.eventData.length).toBeGreaterThan(
|
|
645
|
+
expect(STORAGE.eventData.length).toBeGreaterThan(5);
|
|
646
|
+
expect(STORAGE.scdTableData[0].length).toBeGreaterThan(5);
|
|
554
647
|
expect(STORAGE.eventData.every(e => validEvent(e))).toBeTruthy();
|
|
555
648
|
});
|
|
556
649
|
|
|
@@ -568,7 +661,9 @@ describe('orchestrators', () => {
|
|
|
568
661
|
hasLocation: false,
|
|
569
662
|
events: []
|
|
570
663
|
};
|
|
571
|
-
|
|
664
|
+
const context = createTestContext(config);
|
|
665
|
+
context.setStorage(STORAGE);
|
|
666
|
+
await userLoop(context);
|
|
572
667
|
expect(STORAGE.userProfilesData.length).toBe(2);
|
|
573
668
|
expect(STORAGE.eventData.length).toBe(0);
|
|
574
669
|
});
|
package/tests/testSoup.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { generateLineChart } from '../components/chart.js';
|
|
2
|
-
import { TimeSoup } from '../
|
|
2
|
+
import { TimeSoup } from '../lib/utils/utils.js';
|
|
3
3
|
import dayjs from 'dayjs';
|
|
4
4
|
import { progress } from 'ak-tools';
|
|
5
5
|
import TEST_CASES from './testCases.mjs';
|
package/tests/unit.test.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import generate from '../index.js';
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import utc from "dayjs/plugin/utc.js";
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import * as u from 'ak-tools';
|
|
6
6
|
dayjs.extend(utc);
|
|
7
|
-
|
|
7
|
+
import 'dotenv/config';
|
|
8
8
|
|
|
9
9
|
/** @typedef {import('../types').Dungeon} Config */
|
|
10
10
|
/** @typedef {import('../types').EventConfig} EventConfig */
|
|
@@ -15,7 +15,7 @@ require('dotenv').config();
|
|
|
15
15
|
/** @typedef {import('../types').Funnel} Funnel */
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
import {
|
|
19
19
|
applySkew,
|
|
20
20
|
boxMullerRandom,
|
|
21
21
|
choose,
|
|
@@ -32,7 +32,6 @@ const {
|
|
|
32
32
|
range,
|
|
33
33
|
pickAWinner,
|
|
34
34
|
weighNumRange,
|
|
35
|
-
|
|
36
35
|
fixFirstAndLast,
|
|
37
36
|
generateUser,
|
|
38
37
|
openFinder,
|
|
@@ -54,14 +53,16 @@ const {
|
|
|
54
53
|
validTime,
|
|
55
54
|
interruptArray,
|
|
56
55
|
optimizedBoxMuller,
|
|
57
|
-
|
|
58
56
|
datesBetween,
|
|
59
57
|
weighChoices
|
|
60
|
-
}
|
|
58
|
+
} from '../lib/utils/utils.js';
|
|
59
|
+
|
|
60
|
+
import main from '../index.js';
|
|
61
|
+
import { createHookArray } from '../lib/core/storage.js';
|
|
62
|
+
import { inferFunnels } from '../lib/core/config-validator.js';
|
|
61
63
|
|
|
62
|
-
const main = require('../index.js');
|
|
63
64
|
//todo: test for funnel inference
|
|
64
|
-
const
|
|
65
|
+
const hookArray = createHookArray;
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
describe('timesoup', () => {
|
|
@@ -266,7 +267,7 @@ describe('determinism', () => {
|
|
|
266
267
|
describe('generation', () => {
|
|
267
268
|
|
|
268
269
|
test('user: works', () => {
|
|
269
|
-
const uuid = { guid:
|
|
270
|
+
const uuid = { guid: vi.fn().mockReturnValue('uuid-123') };
|
|
270
271
|
const numDays = 30;
|
|
271
272
|
const user = generateUser('123', { numDays });
|
|
272
273
|
expect(user).toHaveProperty('distinct_id');
|
|
@@ -662,7 +663,7 @@ describe('utilities', () => {
|
|
|
662
663
|
|
|
663
664
|
test('progress: output', () => {
|
|
664
665
|
// @ts-ignore
|
|
665
|
-
const mockStdoutWrite =
|
|
666
|
+
const mockStdoutWrite = vi.spyOn(process.stdout, 'write').mockImplementation(() => { });
|
|
666
667
|
progress([['test', 50]]);
|
|
667
668
|
expect(mockStdoutWrite).toHaveBeenCalled();
|
|
668
669
|
mockStdoutWrite.mockRestore();
|
package/tsconfig.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"include": [
|
|
13
13
|
"**/*.js"
|
|
14
|
-
, "soupTemplates.mjs", "tests/testSoup.mjs", "tests/testCases.mjs", "tests/benchmark/concurrency.mjs", "dungeons/customers/pos.mjs", "scripts/new-project.mjs" ],
|
|
14
|
+
, "soupTemplates.mjs", "tests/testSoup.mjs", "tests/testCases.mjs", "tests/benchmark/concurrency.mjs", "dungeons/customers/pos.mjs", "scripts/new-project.mjs", "scripts/dana.mjs" ],
|
|
15
15
|
"exclude": [
|
|
16
16
|
"node_modules"
|
|
17
17
|
]
|
package/types.d.ts
CHANGED
|
@@ -26,10 +26,10 @@ declare namespace main {
|
|
|
26
26
|
concurrency?: number;
|
|
27
27
|
batchSize?: number;
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
serviceAccount?: string;
|
|
30
|
+
serviceSecret?: string;
|
|
31
|
+
projectId?: string;
|
|
32
|
+
|
|
33
33
|
// ids
|
|
34
34
|
simulationName?: string;
|
|
35
35
|
name?: string;
|
|
@@ -50,7 +50,7 @@ declare namespace main {
|
|
|
50
50
|
hasSessionIds?: boolean;
|
|
51
51
|
alsoInferFunnels?: boolean;
|
|
52
52
|
makeChart?: boolean | string;
|
|
53
|
-
|
|
53
|
+
singleCountry?: string;
|
|
54
54
|
|
|
55
55
|
//models
|
|
56
56
|
events?: EventConfig[]; //| string[]; //can also be a array of strings
|
|
@@ -69,22 +69,18 @@ declare namespace main {
|
|
|
69
69
|
//allow anything to be on the config
|
|
70
70
|
[key: string]: any;
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
//probabilities
|
|
73
|
+
percentUsersBornInDataset?: number;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export type
|
|
77
|
-
type?: string | "
|
|
76
|
+
export type SCDProp = {
|
|
77
|
+
type?: string | "user" | "company_id" | "team_id" | "department_id";
|
|
78
78
|
frequency: "day" | "week" | "month" | "year";
|
|
79
79
|
values: ValueValid;
|
|
80
80
|
timing: "fixed" | "fuzzy";
|
|
81
81
|
max?: number;
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
export type SimpleSCDProp = string[];
|
|
85
|
-
|
|
86
|
-
export type SCDProp = complexSCDProp | SimpleSCDProp;
|
|
87
|
-
|
|
88
84
|
/**
|
|
89
85
|
* the soup is a set of parameters that determine the distribution of events over time
|
|
90
86
|
*/
|
|
@@ -160,6 +156,64 @@ declare namespace main {
|
|
|
160
156
|
groupEventData?: HookedArray<EventSchema>;
|
|
161
157
|
}
|
|
162
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Runtime state for tracking execution metrics and flags
|
|
161
|
+
*/
|
|
162
|
+
export interface RuntimeState {
|
|
163
|
+
operations: number;
|
|
164
|
+
eventCount: number;
|
|
165
|
+
userCount: number;
|
|
166
|
+
isBatchMode: boolean;
|
|
167
|
+
verbose: boolean;
|
|
168
|
+
isCLI: boolean;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Default data factories for generating realistic test data
|
|
173
|
+
*/
|
|
174
|
+
export interface Defaults {
|
|
175
|
+
locationsUsers: () => any[];
|
|
176
|
+
locationsEvents: () => any[];
|
|
177
|
+
iOSDevices: () => any[];
|
|
178
|
+
androidDevices: () => any[];
|
|
179
|
+
desktopDevices: () => any[];
|
|
180
|
+
browsers: () => any[];
|
|
181
|
+
campaigns: () => any[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Context object that replaces global variables with dependency injection
|
|
186
|
+
* Contains validated config, storage containers, defaults, and runtime state
|
|
187
|
+
*/
|
|
188
|
+
export interface Context {
|
|
189
|
+
config: Dungeon;
|
|
190
|
+
storage: Storage | null;
|
|
191
|
+
defaults: Defaults;
|
|
192
|
+
campaigns: any[];
|
|
193
|
+
runtime: RuntimeState;
|
|
194
|
+
FIXED_NOW: number;
|
|
195
|
+
FIXED_BEGIN?: number;
|
|
196
|
+
|
|
197
|
+
// State update methods
|
|
198
|
+
incrementOperations(): void;
|
|
199
|
+
incrementEvents(): void;
|
|
200
|
+
incrementUsers(): void;
|
|
201
|
+
setStorage(storage: Storage): void;
|
|
202
|
+
|
|
203
|
+
// State getter methods
|
|
204
|
+
getOperations(): number;
|
|
205
|
+
getEventCount(): number;
|
|
206
|
+
getUserCount(): number;
|
|
207
|
+
incrementUserCount(): void;
|
|
208
|
+
incrementEventCount(): void;
|
|
209
|
+
isBatchMode(): boolean;
|
|
210
|
+
isCLI(): boolean;
|
|
211
|
+
|
|
212
|
+
// Time helper methods
|
|
213
|
+
getTimeShift(): number;
|
|
214
|
+
getDaysShift(): number;
|
|
215
|
+
}
|
|
216
|
+
|
|
163
217
|
/**
|
|
164
218
|
* how we define events and their properties
|
|
165
219
|
*/
|
|
@@ -169,6 +223,7 @@ declare namespace main {
|
|
|
169
223
|
properties?: Record<string, ValueValid>;
|
|
170
224
|
isFirstEvent?: boolean;
|
|
171
225
|
isChurnEvent?: boolean;
|
|
226
|
+
isSessionStartEvent?: boolean;
|
|
172
227
|
relativeTimeMs?: number;
|
|
173
228
|
}
|
|
174
229
|
|
|
@@ -334,10 +389,10 @@ declare namespace main {
|
|
|
334
389
|
eventData: EventSchema[];
|
|
335
390
|
mirrorEventData: EventSchema[];
|
|
336
391
|
userProfilesData: any[];
|
|
337
|
-
scdTableData: any[];
|
|
392
|
+
scdTableData: any[][];
|
|
338
393
|
adSpendData: EventSchema[];
|
|
339
|
-
groupProfilesData: GroupProfileSchema[];
|
|
340
|
-
lookupTableData: LookupTableData[];
|
|
394
|
+
groupProfilesData: GroupProfileSchema[][];
|
|
395
|
+
lookupTableData: LookupTableData[][];
|
|
341
396
|
importResults?: ImportResults;
|
|
342
397
|
files?: string[];
|
|
343
398
|
time?: {
|
|
@@ -346,6 +401,9 @@ declare namespace main {
|
|
|
346
401
|
delta: number;
|
|
347
402
|
human: string;
|
|
348
403
|
};
|
|
404
|
+
operations?: number;
|
|
405
|
+
eventCount?: number;
|
|
406
|
+
userCount?: number;
|
|
349
407
|
};
|
|
350
408
|
}
|
|
351
409
|
|
|
@@ -353,8 +411,8 @@ declare namespace main {
|
|
|
353
411
|
* Mixpanel Data Generator
|
|
354
412
|
* model events, users, groups, and lookup tables (and SCD props!)
|
|
355
413
|
* @example
|
|
356
|
-
*
|
|
357
|
-
* const
|
|
414
|
+
* import datagenerator from 'make-mp-data';
|
|
415
|
+
* const data = await datagenerator({...opts});
|
|
358
416
|
*/
|
|
359
417
|
declare function main(config: main.Dungeon): Promise<main.Result>;
|
|
360
418
|
|