make-mp-data 1.4.5 → 1.5.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.
@@ -0,0 +1,14 @@
1
+ import project from "../components/project.js";
2
+ import dotenv from "dotenv";
3
+
4
+ dotenv.config();
5
+ const OAUTH_TOKEN = process.env.OAUTH_TOKEN || "";
6
+
7
+ if (!OAUTH_TOKEN) throw new Error('No OAUTH_TOKEN in .env');
8
+
9
+ // @ts-ignore
10
+ const createdProject = await project({
11
+ oauth: OAUTH_TOKEN
12
+ })
13
+
14
+ console.log(createdProject);
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ npm i mixpanel-import@latest --save
3
+ npm i ak-tools@latest --save
4
+ npm i ak-fetch@latest --save
@@ -0,0 +1,52 @@
1
+ /*
2
+ ----
3
+ TO DOs
4
+ ----
5
+ */
6
+
7
+ //!feature: fixedTimeFunnel? if set this funnel will occur for all users at the same time ['cards charged', 'charge complete']
8
+ //!feature: churn ... is churnFunnel, possible to return, etc
9
+ //!feature: send SCD data to mixpanel (blocked on dev)
10
+ //!feature: send and map lookup tables to mixpanel (also blocked on dev)
11
+ //!bug: using --mc flag reverts to --complex for some reason
12
+
13
+
14
+ import main from "../../index.js";
15
+ import simple from '../../schemas/simple.js';
16
+
17
+ /** @typedef {import('../../types').Config} Config */
18
+
19
+ /** @type {Config} */
20
+ const noWrites = {
21
+ ...simple,
22
+ numUsers: 10_000,
23
+ numEvents: 250_000,
24
+ writeToDisk: false,
25
+ };
26
+
27
+ /** @type {Config} */
28
+ const yesWrites = {
29
+ ...noWrites,
30
+ writeToDisk: true
31
+ };
32
+
33
+ console.log('concurrency benchmarking');
34
+
35
+ const concurrency = [1, 2, 3, 4, 5];
36
+
37
+ const results = [];
38
+ for (const concurrent of concurrency) {
39
+ console.log(`concurrency: ${concurrent}`);
40
+ // @ts-ignore
41
+ const test = await main({ ...noWrites, concurrency: concurrent });
42
+ results.push({ human: test.time.human, concurrency: concurrent });
43
+ console.log(`\t\tdone: ${test.time.human}\n\n`);
44
+ }
45
+
46
+ const display = results.map((r) => {
47
+ return `concurrency: ${r.concurrency} | duration: ${r.human}`;
48
+ });
49
+
50
+ console.log(display.join('\n\n'));
51
+
52
+ debugger;
package/tests/e2e.test.js CHANGED
@@ -3,19 +3,22 @@
3
3
  /* eslint-disable no-undef */
4
4
  /* eslint-disable no-debugger */
5
5
  /* eslint-disable no-unused-vars */
6
- const generate = require('../core/index.js');
6
+ const generate = require('../index.js');
7
7
  require('dotenv').config();
8
8
  const { execSync } = require("child_process");
9
9
  const u = require('ak-tools');
10
+ const Papa = require('papaparse');
10
11
 
11
12
  const simple = require('../schemas/simple.js');
12
13
  const complex = require('../schemas/complex.js');
13
14
  const anon = require('../schemas/anon.js');
14
15
  const funnels = require('../schemas/funnels.js');
15
16
  const foobar = require('../schemas/foobar.js');
17
+ const mirror = require('../schemas/mirror.js');
18
+ const adspend = require('../schemas/adspend.js');
16
19
 
17
- const timeout = 60000;
18
- const testToken = process.env.TEST_TOKEN;
20
+ const timeout = 600000;
21
+ const testToken = process.env.TEST_TOKEN || "hello token!";
19
22
 
20
23
  describe('module', () => {
21
24
 
@@ -48,14 +51,61 @@ describe('module', () => {
48
51
  const results = await generate({ ...complex, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
49
52
  const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
50
53
  expect(eventData.length).toBeGreaterThan(980);
51
- expect(groupProfilesData[0]?.data?.length).toBe(500);
54
+ expect(groupProfilesData[0]?.length).toBe(500);
52
55
  expect(lookupTableData.length).toBe(2);
53
- expect(lookupTableData[0].data.length).toBe(1000);
56
+ expect(lookupTableData[0].length).toBe(1000);
54
57
  expect(scdTableData.length).toBe(5);
55
58
  expect(userProfilesData.length).toBe(100);
56
59
 
57
60
  }, timeout);
58
61
 
62
+ test('works as module (funnels)', async () => {
63
+ console.log('MODULE TEST: FUNNELS');
64
+ const results = await generate({ ...funnels, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
65
+ const { eventData, groupProfilesData, scdTableData, userProfilesData } = results;
66
+ expect(eventData.length).toBeGreaterThan(980);
67
+ expect(groupProfilesData.length).toBe(3);
68
+ expect(groupProfilesData[0]?.length).toBe(5000);
69
+ expect(groupProfilesData[1]?.length).toBe(500);
70
+ expect(groupProfilesData[2]?.length).toBe(50);
71
+ expect(scdTableData.length).toBe(2);
72
+ expect(scdTableData[0]?.length).toBeGreaterThan(200);
73
+ expect(scdTableData[1]?.length).toBeGreaterThan(200);
74
+ expect(userProfilesData.length).toBe(100);
75
+
76
+ }, timeout);
77
+
78
+ test('works as module (mirror)', async () => {
79
+ console.log('MODULE TEST: MIRROR');
80
+ const results = await generate({ ...mirror, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
81
+ const { eventData, userProfilesData, mirrorEventData } = results;
82
+ expect(eventData.length).toBeGreaterThan(980);
83
+ expect(mirrorEventData.length).toBeGreaterThan(980);
84
+ expect(mirrorEventData.every(e => e.newlyCreated)).toBe(true);
85
+ expect(eventData.every(e => e.newlyCreated)).toBe(false);
86
+ expect(userProfilesData.length).toBe(100);
87
+
88
+ }, timeout);
89
+
90
+ test('works as module (foobar)', async () => {
91
+ console.log('MODULE TEST: FOOBAR');
92
+ const results = await generate({ ...foobar, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
93
+ const { eventData, userProfilesData } = results;
94
+ expect(eventData.length).toBeGreaterThan(980);
95
+ expect(userProfilesData.length).toBe(100);
96
+
97
+ }, timeout);
98
+
99
+ test('works as module (adspend)', async () => {
100
+ console.log('MODULE TEST: ADSPEND');
101
+ const results = await generate({ ...adspend, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
102
+ const { eventData, adSpendData, userProfilesData } = results;
103
+ expect(eventData.length).toBeGreaterThan(980);
104
+ expect(userProfilesData.length).toBe(100);
105
+ expect(adSpendData.length).toBe(14600);
106
+
107
+ }, timeout);
108
+
59
109
 
60
110
  test('fails with invalid configuration', async () => {
61
111
  try {
@@ -68,75 +118,210 @@ describe('module', () => {
68
118
 
69
119
  test('works with no params', async () => {
70
120
  const { eventData, userProfilesData, groupProfilesData, files, importResults, lookupTableData, mirrorEventData, scdTableData } = await generate({ writeToDisk: false });
71
- debugger;
121
+ expect(eventData.length).toBeGreaterThan(100000);
122
+ expect(userProfilesData.length).toBe(1000);
123
+ expect(groupProfilesData.length).toBe(0);
124
+ expect(importResults).toBe(undefined);
125
+ expect(scdTableData.length).toBe(0);
126
+ expect(lookupTableData.length).toBe(0);
127
+ expect(mirrorEventData.length).toBe(0);
72
128
  }, timeout);
73
129
 
74
130
 
75
131
 
76
132
 
133
+ });
134
+
135
+ describe('batching', () => {
136
+ test('batch writes', async () => {
137
+ const results = await generate({ ...foobar, batchSize: 1000, writeToDisk: true, numEvents: 10_000, numUsers: 5000, seed: "deal" });
138
+ const { eventData, userProfilesData } = results;
139
+ const files = (await u.ls('./data')).filter(a => a.endsWith('.json'));
140
+ const eventFiles = files.filter(a => a.includes('EVENTS'));
141
+ const userFiles = files.filter(a => a.includes('USERS'));
142
+ const evWriteDir = eventData.getWriteDir();
143
+ const usWriteDir = userProfilesData.getWriteDir();
144
+ const evWritePath = eventData.getWritePath();
145
+ const usWritePath = userProfilesData.getWritePath();
146
+
147
+ const expectedEvWriteDir = `-EVENTS.json`;
148
+ const expectedUsWriteDir = `-USERS.json`;
149
+ const expectedWritePath = `-part-`;
150
+
151
+ expect(eventFiles.length).toBe(23);
152
+ expect(userFiles.length).toBe(5);
153
+
154
+ expect(eventFiles.filter(a => a.includes('part')).length).toBe(23);
155
+ expect(userFiles.filter(a => a.includes('part')).length).toBe(5);
156
+ expect(evWriteDir.endsWith(expectedEvWriteDir)).toBe(true);
157
+ expect(usWriteDir.endsWith(expectedUsWriteDir)).toBe(true);
158
+ expect(evWritePath.includes(expectedWritePath)).toBe(true);
159
+ expect(usWritePath.includes(expectedWritePath)).toBe(true);
160
+
161
+ }, timeout);
162
+
163
+
164
+ test('dont batch', async () => {
165
+ const results = await generate({ ...foobar, writeToDisk: true, numEvents: 5000, numUsers: 1000, seed: "deal" });
166
+ const { eventData, userProfilesData } = results;
167
+ const files = await u.ls('./data');
168
+ const eventFiles = files.filter(a => a.includes('EVENTS'));
169
+ const userFiles = files.filter(a => a.includes('USERS'));
170
+ expect(eventFiles.length).toBe(1);
171
+ expect(userFiles.length).toBe(1);
172
+ expect(eventFiles.filter(a => a.includes('part')).length).toBe(0);
173
+ const evWriteDir = eventData.getWriteDir();
174
+ const usWriteDir = userProfilesData.getWriteDir();
175
+ const expectedEvWriteDir = `-EVENTS.json`;
176
+ const expectedUsWriteDir = `-USERS.json`;
177
+ expect(evWriteDir.endsWith(expectedEvWriteDir)).toBe(true);
178
+ expect(usWriteDir.endsWith(expectedUsWriteDir)).toBe(true);
179
+
180
+ const evWritePath = eventData.getWritePath();
181
+ const usWritePath = userProfilesData.getWritePath();
182
+ expect(evWritePath.endsWith(expectedEvWriteDir)).toBe(true);
183
+ expect(usWritePath.endsWith(expectedUsWriteDir)).toBe(true);
184
+
185
+ }, timeout);
186
+
187
+ test('send to mp: batches', async () => {
188
+ const results = await generate({ ...foobar, numDays: 90, hasAdSpend: true, token: testToken, batchSize: 4500, writeToDisk: true, numEvents: 10_000, numUsers: 5000, seed: "deal" });
189
+ const { importResults } = results;
190
+ const { adSpend, events, groups, users } = importResults;
191
+ expect(adSpend.success).toBeGreaterThan(0);
192
+ expect(events.success).toBeGreaterThan(0);
193
+ expect(users.success).toBeGreaterThan(0);
194
+ expect(groups[0].success).toBeGreaterThan(0);
195
+ expect(groups[1].success).toBeGreaterThan(0);
196
+ expect(adSpend.success).toBe(adSpend.total);
197
+ expect(events.success).toBe(events.total);
198
+ // expect(users.success).toBe(users.total);
199
+ expect(groups.length).toBe(2);
200
+ expect(groups[0].success).toBe(groups[0].total);
201
+ expect(groups[1].success).toBe(groups[1].total);
202
+ expect(adSpend.failed).toBe(0);
203
+ expect(events.failed).toBe(0);
204
+ expect(users.failed).toBe(0);
205
+ expect(groups[0].failed).toBe(0);
206
+ expect(groups[1].failed).toBe(0);
207
+
208
+
209
+ }, timeout);
210
+
211
+ test('send to mp: no batch', async () => {
212
+ const results = await generate({ ...foobar, numDays: 90, hasAdSpend: true, token: testToken, writeToDisk: true, numEvents: 5000, numUsers: 1000, seed: "deal" });
213
+ const { importResults } = results;
214
+ const { adSpend, events, groups, users } = importResults;
215
+ expect(adSpend.success).toBeGreaterThan(0);
216
+ expect(events.success).toBeGreaterThan(0);
217
+ expect(users.success).toBeGreaterThan(0);
218
+ expect(groups[0].success).toBeGreaterThan(0);
219
+ expect(groups[1].success).toBeGreaterThan(0);
220
+ expect(adSpend.success).toBe(adSpend.total);
221
+ expect(events.success).toBe(events.total);
222
+ expect(users.success).toBe(users.total);
223
+ expect(groups.length).toBe(2);
224
+ expect(groups[0].success).toBe(groups[0].total);
225
+ expect(groups[1].success).toBe(groups[1].total);
226
+ expect(adSpend.failed).toBe(0);
227
+ expect(events.failed).toBe(0);
228
+ expect(users.failed).toBe(0);
229
+ expect(groups[0].failed).toBe(0);
230
+ expect(groups[1].failed).toBe(0);
231
+
232
+ }, timeout);
77
233
  });
78
234
 
79
235
  describe('cli', () => {
80
- test('works as CLI (no args)', async () => {
81
- console.log('COMPLEX CLI TEST');
82
- const run = execSync(`node ./core/index.js --numEvents 1000 --numUsers 100`, { stdio: 'ignore' });
83
- // expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
236
+
237
+ test('sanity check', async () => {
238
+ console.log('SANITY TEST');
239
+ const run = execSync(`node ./index.js`);
240
+ const ending = `enjoy your data! :)`;
241
+ expect(run.toString().trim().endsWith(ending)).toBe(true);
242
+ const files = (await u.ls('./data')).filter(a => a.includes('.csv'));
243
+ expect(files.length).toBe(2);
244
+ const users = files.filter(a => a.includes('USERS'));
245
+ const events = files.filter(a => a.includes('EVENTS'));
246
+ expect(users.length).toBe(1);
247
+ expect(events.length).toBe(1);
248
+ const eventData = (await u.load(events[0])).trim();
249
+ const userProfilesData = (await u.load(users[0])).trim();
250
+ const parsedEvents = Papa.parse(eventData, { header: true }).data;
251
+ const parsedUsers = Papa.parse(userProfilesData, { header: true }).data;
252
+ expect(parsedEvents.length).toBeGreaterThan(42000);
253
+ expect(parsedUsers.length).toBeGreaterThan(420);
254
+ expect(parsedUsers.every(u => u.distinct_id)).toBe(true);
255
+ expect(parsedEvents.every(e => e.event)).toBe(true);
256
+ expect(parsedEvents.every(e => e.time)).toBe(true);
257
+ expect(parsedEvents.every(e => e.insert_id)).toBe(true);
258
+ expect(parsedEvents.every(e => e.device_id || e.user_id)).toBe(true);
259
+ expect(parsedUsers.every(u => u.name)).toBe(true);
260
+ expect(parsedUsers.every(u => u.email)).toBe(true);
261
+ expect(parsedUsers.every(u => u.created)).toBe(true);
262
+ expect(parsedUsers.every(u => u.avatar)).toBe(false);
263
+ expect(parsedEvents.every(e => validateEvent(e))).toBe(true);
264
+ expect(parsedUsers.every(u => validateUser(u))).toBe(true);
265
+ }, timeout);
266
+
267
+ test('no args', async () => {
268
+ console.log('BARE CLI TEST');
269
+ const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100`);
270
+ expect(run.toString().trim().includes('enjoy your data! :)')).toBe(true);
84
271
  const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
85
272
  expect(csvs.length).toBe(2);
86
- clearData();
273
+
87
274
  }, timeout);
88
275
 
89
- test('works as CLI (complex)', async () => {
276
+ test('--complex', async () => {
90
277
  console.log('COMPLEX CLI TEST');
91
- const run = execSync(`node ./core/index.js --numEvents 1000 --numUsers 100 --seed "deal with it" --complex`, { stdio: 'ignore' });
92
- // expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
278
+ const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it" --complex`, { stdio: "ignore" });
93
279
  const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
94
280
  expect(csvs.length).toBe(13);
95
- clearData();
281
+
96
282
  }, timeout);
97
283
 
98
- test('works as CLI (simple)', async () => {
99
- console.log('simple CLI TEST');
100
- const run = execSync(`node ./core/index.js --numEvents 1000 --numUsers 100 --seed "deal with it" --simple`);
101
- expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
284
+ test('--simple', async () => {
285
+ console.log('SIMPLE CLI TEST');
286
+ const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it" --simple`);
287
+ expect(run.toString().trim().includes('enjoy your data! :)')).toBe(true);
102
288
  const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
103
289
  expect(csvs.length).toBe(2);
104
- clearData();
290
+
105
291
  }, timeout);
106
292
 
107
293
 
108
294
  });
109
295
 
110
-
111
296
  describe('options + tweaks', () => {
112
297
  test('creates sessionIds', async () => {
113
- const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, sessionIds: true });
298
+ const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, hasSessionIds: true });
114
299
  const { eventData } = results;
115
300
  const sessionIds = eventData.map(a => a.session_id).filter(a => a);
116
301
  expect(sessionIds.length).toBe(eventData.length);
117
302
  }, timeout);
118
303
 
119
- test('no sessionIds', async () => {
120
- const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, sessionIds: false });
304
+ test('no hasSessionIds', async () => {
305
+ const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, hasSessionIds: false });
121
306
  const { eventData } = results;
122
- const sessionIds = eventData.map(a => a.session_id).filter(a => a);
123
- expect(sessionIds.length).toBe(0);
307
+ const noSessionIds = eventData.map(a => a.session_id).filter(a => a);
308
+ expect(noSessionIds.length).toBe(0);
124
309
  }, timeout);
125
310
 
126
311
  test('creates anonymousIds', async () => {
127
- const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, anonIds: true });
312
+ const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, hasAnonIds: true });
128
313
  const { eventData } = results;
129
- const anonIds = eventData.map(a => a.device_id).filter(a => a);
314
+ const anonymousEvents = eventData.map(a => a.device_id).filter(a => a);
130
315
  const userIds = eventData.map(a => a.user_id).filter(a => a);
131
- expect(anonIds.length).toBe(eventData.length);
132
- expect(userIds.length).toBeLessThan(anonIds.length);
316
+ expect(anonymousEvents.length).toBe(eventData.length);
317
+ expect(userIds.length).toBeLessThan(anonymousEvents.length);
133
318
  }, timeout);
134
319
 
135
320
  test('no anonymousIds', async () => {
136
- const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, anonIds: false });
321
+ const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, hasAnonIds: false });
137
322
  const { eventData } = results;
138
- const anonIds = eventData.map(a => a.device_id).filter(a => a);
139
- expect(anonIds.length).toBe(0);
323
+ const unanonymousEvents = eventData.map(a => a.device_id).filter(a => a);
324
+ expect(unanonymousEvents.length).toBe(0);
140
325
  }, timeout);
141
326
 
142
327
  test('sends data to mixpanel', async () => {
@@ -150,7 +335,7 @@ describe('options + tweaks', () => {
150
335
 
151
336
  test('every record is valid', async () => {
152
337
  console.log('VALIDATION TEST');
153
- const results = await generate({ verbose: true, writeToDisk: false, numEvents: 10000, numUsers: 500 });
338
+ const results = await generate({ verbose: true, writeToDisk: false, numEvents: 1000, numUsers: 100 });
154
339
  const { eventData, userProfilesData } = results;
155
340
  const areEventsValid = eventData.every(validateEvent);
156
341
  const areUsersValid = userProfilesData.every(validateUser);
@@ -164,24 +349,44 @@ describe('options + tweaks', () => {
164
349
 
165
350
  test('every date is valid', async () => {
166
351
  console.log('DATE TEST');
167
- const results = await generate({ ...simple, writeToDisk: false, verbose: true });
352
+ const results = await generate({ ...simple, writeToDisk: false, verbose: true, numEvents: 1000, numUsers: 100 });
168
353
  const { eventData } = results;
169
- const invalidDates = eventData.filter(e => !validateTime(e.time));
170
- expect(eventData.every(e => validateTime(e.time))).toBe(true);
354
+ const invalidDates = eventData.filter(e => !validTime(e.time));
355
+ expect(eventData.every(e => validTime(e.time))).toBe(true);
171
356
 
172
357
  }, timeout);
173
358
 
174
359
  test('anonymous users', async () => {
175
360
  console.log('ANON TEST');
176
- const results = await generate({ ...anon, writeToDisk: false, verbose: true });
361
+ const results = await generate({ ...anon, writeToDisk: false, verbose: true, numEvents: 1000, numUsers: 100 });
177
362
  const { userProfilesData } = results;
178
363
  expect(userProfilesData.every(u => u.name === 'Anonymous User')).toBe(true);
179
364
 
180
365
  }, timeout);
181
366
 
367
+ test('no avatars (default)', async () => {
368
+ console.log('AVATAR TEST');
369
+ const results = await generate({ ...simple, writeToDisk: false, verbose: true, numEvents: 1000, numUsers: 100 });
370
+ const { userProfilesData } = results;
371
+ expect(userProfilesData.every(u => !u.avatar)).toBe(true);
372
+
373
+ }, timeout);
374
+
375
+ test('yes avatars', async () => {
376
+ console.log('AVATAR TEST');
377
+ const results = await generate({ ...simple, writeToDisk: false, verbose: true, numEvents: 1000, numUsers: 100, hasAvatar: true });
378
+ const { userProfilesData } = results;
379
+ expect(userProfilesData.every(u => u.avatar)).toBe(true);
380
+
381
+ }, timeout);
382
+
383
+ });
384
+
385
+ beforeEach(() => {
386
+ clearData();
182
387
  });
183
388
 
184
- afterAll(() => {
389
+ afterEach(() => {
185
390
  clearData();
186
391
  });
187
392
 
@@ -216,7 +421,7 @@ function validateUser(user) {
216
421
  }
217
422
 
218
423
 
219
- function validateTime(str) {
424
+ function validTime(str) {
220
425
  if (!str) return false;
221
426
  if (str.startsWith('-')) return false;
222
427
  if (!str.startsWith('20')) return false;