make-mp-data 1.0.12 → 1.0.14
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/default.js +2 -2
- package/index.js +106 -46
- package/package.json +1 -1
package/default.js
CHANGED
|
@@ -6,8 +6,8 @@ const config = {
|
|
|
6
6
|
token: "",
|
|
7
7
|
seed: "foo bar baz",
|
|
8
8
|
numDays: 30, //how many days worth of data
|
|
9
|
-
numEvents:
|
|
10
|
-
numUsers:
|
|
9
|
+
numEvents: 100000, //how many events
|
|
10
|
+
numUsers: 1000, //how many users
|
|
11
11
|
format: 'csv', //csv or json
|
|
12
12
|
region: "US",
|
|
13
13
|
|
package/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
/*
|
|
5
4
|
make fake mixpanel data easily!
|
|
6
5
|
by AK
|
|
@@ -25,21 +24,25 @@ const {
|
|
|
25
24
|
choose,
|
|
26
25
|
range,
|
|
27
26
|
exhaust,
|
|
28
|
-
openFinder
|
|
27
|
+
openFinder,
|
|
29
28
|
} = require("./utils.js");
|
|
30
29
|
const dayjs = require("dayjs");
|
|
31
30
|
const utc = require("dayjs/plugin/utc");
|
|
32
|
-
const cliParams = require(
|
|
33
|
-
|
|
31
|
+
const cliParams = require("./cli.js");
|
|
34
32
|
|
|
35
33
|
dayjs.extend(utc);
|
|
36
34
|
Array.prototype.pickOne = pick;
|
|
37
35
|
const now = dayjs().unix();
|
|
38
36
|
const dayInSec = 86400;
|
|
37
|
+
const PEAK_DAYS = [
|
|
38
|
+
dayjs().subtract(1, "day").unix(),
|
|
39
|
+
dayjs().subtract(5, "day").unix(),
|
|
40
|
+
dayjs().subtract(10, "day").unix(),
|
|
41
|
+
dayjs().subtract(15, "day").unix(),
|
|
42
|
+
];
|
|
39
43
|
|
|
40
44
|
//our main program
|
|
41
45
|
async function main(config) {
|
|
42
|
-
|
|
43
46
|
let {
|
|
44
47
|
seed = "every time a rug is micturated upon in this fair city...",
|
|
45
48
|
numEvents = 100000,
|
|
@@ -58,7 +61,7 @@ async function main(config) {
|
|
|
58
61
|
format = "csv",
|
|
59
62
|
token = null,
|
|
60
63
|
region = "US",
|
|
61
|
-
writeToDisk = false
|
|
64
|
+
writeToDisk = false,
|
|
62
65
|
} = config;
|
|
63
66
|
if (require.main === module) writeToDisk = true;
|
|
64
67
|
const uuidChance = new Chance(seed);
|
|
@@ -84,7 +87,6 @@ async function main(config) {
|
|
|
84
87
|
}, [])
|
|
85
88
|
.filter((e) => !e.isFirstEvent);
|
|
86
89
|
|
|
87
|
-
|
|
88
90
|
const firstEvents = events.filter((e) => e.isFirstEvent);
|
|
89
91
|
const eventData = [];
|
|
90
92
|
const userProfilesData = [];
|
|
@@ -146,7 +148,7 @@ async function main(config) {
|
|
|
146
148
|
const group = {
|
|
147
149
|
[groupKey]: i,
|
|
148
150
|
...makeProfile(groupProps[groupKey]),
|
|
149
|
-
$distinct_id: i
|
|
151
|
+
$distinct_id: i,
|
|
150
152
|
};
|
|
151
153
|
groupProfiles.push(group);
|
|
152
154
|
}
|
|
@@ -168,7 +170,8 @@ async function main(config) {
|
|
|
168
170
|
}
|
|
169
171
|
lookupTableData.push({ key, data });
|
|
170
172
|
}
|
|
171
|
-
const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, folder } =
|
|
173
|
+
const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, folder } =
|
|
174
|
+
buildFileNames(config);
|
|
172
175
|
const pairs = [
|
|
173
176
|
[eventFiles, eventData],
|
|
174
177
|
[userFiles, userProfilesData],
|
|
@@ -178,13 +181,20 @@ async function main(config) {
|
|
|
178
181
|
];
|
|
179
182
|
console.log("\n");
|
|
180
183
|
|
|
181
|
-
if (!writeToDisk && !token)
|
|
184
|
+
if (!writeToDisk && !token)
|
|
185
|
+
return {
|
|
186
|
+
eventData,
|
|
187
|
+
userProfilesData,
|
|
188
|
+
scdTableData,
|
|
189
|
+
groupProfilesData,
|
|
190
|
+
lookupTableData,
|
|
191
|
+
};
|
|
182
192
|
//write the files
|
|
183
193
|
for (const pair of pairs) {
|
|
184
194
|
const [paths, data] = pair;
|
|
185
195
|
for (const path of paths) {
|
|
186
196
|
let datasetsToWrite;
|
|
187
|
-
if (data?.[0]?.["key"]) datasetsToWrite = data.map(d => d.data);
|
|
197
|
+
if (data?.[0]?.["key"]) datasetsToWrite = data.map((d) => d.data);
|
|
188
198
|
else datasetsToWrite = [data];
|
|
189
199
|
for (const writeData of datasetsToWrite) {
|
|
190
200
|
if (format === "csv") {
|
|
@@ -210,37 +220,47 @@ async function main(config) {
|
|
|
210
220
|
strict: false,
|
|
211
221
|
dryRun: false,
|
|
212
222
|
abridged: false,
|
|
213
|
-
region
|
|
214
|
-
|
|
223
|
+
region,
|
|
215
224
|
};
|
|
216
225
|
//send to mixpanel
|
|
217
226
|
if (token) {
|
|
218
227
|
if (eventData) {
|
|
219
228
|
console.log(`importing events to mixpanel...`);
|
|
220
|
-
const imported = await mp(creds, eventData, {
|
|
229
|
+
const imported = await mp(creds, eventData, {
|
|
230
|
+
recordType: "event",
|
|
231
|
+
...importOpts,
|
|
232
|
+
});
|
|
221
233
|
console.log(`\tsent ${comma(imported.success)} events\n`);
|
|
222
234
|
importResults.events = imported;
|
|
223
235
|
}
|
|
224
236
|
if (userProfilesData) {
|
|
225
237
|
console.log(`importing user profiles to mixpanel...`);
|
|
226
|
-
const imported = await mp(creds, userProfilesData, {
|
|
238
|
+
const imported = await mp(creds, userProfilesData, {
|
|
239
|
+
recordType: "user",
|
|
240
|
+
...importOpts,
|
|
241
|
+
});
|
|
227
242
|
console.log(`\tsent ${comma(imported.success)} user profiles\n`);
|
|
228
243
|
importResults.users = imported;
|
|
229
244
|
}
|
|
230
245
|
if (groupProfilesData) {
|
|
231
|
-
|
|
232
246
|
for (const groupProfiles of groupProfilesData) {
|
|
233
247
|
const groupKey = groupProfiles.key;
|
|
234
248
|
const data = groupProfiles.data;
|
|
235
249
|
console.log(`importing ${groupKey} profiles to mixpanel...`);
|
|
236
|
-
const imported = await mp({ token, groupKey }, data, {
|
|
250
|
+
const imported = await mp({ token, groupKey }, data, {
|
|
251
|
+
recordType: "group",
|
|
252
|
+
...importOpts,
|
|
253
|
+
});
|
|
237
254
|
console.log(`\tsent ${comma(imported.success)} ${groupKey} profiles\n`);
|
|
238
255
|
importResults.groups.push(imported);
|
|
239
256
|
}
|
|
240
257
|
}
|
|
241
258
|
console.log(`\n\n`);
|
|
242
259
|
}
|
|
243
|
-
return {
|
|
260
|
+
return {
|
|
261
|
+
import: importResults,
|
|
262
|
+
files: [eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, folder],
|
|
263
|
+
};
|
|
244
264
|
}
|
|
245
265
|
|
|
246
266
|
function makeProfile(props, defaults) {
|
|
@@ -280,7 +300,14 @@ function makeSCD(props, distinct_id, mutations, $created) {
|
|
|
280
300
|
return scdEntries;
|
|
281
301
|
}
|
|
282
302
|
|
|
283
|
-
function makeEvent(
|
|
303
|
+
function makeEvent(
|
|
304
|
+
distinct_id,
|
|
305
|
+
earliestTime,
|
|
306
|
+
events,
|
|
307
|
+
superProps,
|
|
308
|
+
groupKeys,
|
|
309
|
+
isFirstEvent = false
|
|
310
|
+
) {
|
|
284
311
|
let chosenEvent = events.pickOne();
|
|
285
312
|
if (typeof chosenEvent === "string")
|
|
286
313
|
chosenEvent = { event: chosenEvent, properties: {} };
|
|
@@ -291,7 +318,7 @@ function makeEvent(distinct_id, earliestTime, events, superProps, groupKeys, isF
|
|
|
291
318
|
};
|
|
292
319
|
|
|
293
320
|
if (isFirstEvent) event.time = earliestTime;
|
|
294
|
-
if (!isFirstEvent) event.time =
|
|
321
|
+
if (!isFirstEvent) event.time = customTimeDistribution(earliestTime, now, PEAK_DAYS);
|
|
295
322
|
|
|
296
323
|
const props = { ...chosenEvent.properties, ...superProps };
|
|
297
324
|
|
|
@@ -318,8 +345,8 @@ function buildFileNames(config) {
|
|
|
318
345
|
const { format = "csv", groupKeys = [], lookupTables = [] } = config;
|
|
319
346
|
const extension = format === "csv" ? "csv" : "json";
|
|
320
347
|
const current = dayjs.utc().format("MM-DD-HH");
|
|
321
|
-
let writeDir =
|
|
322
|
-
if (config.writeToDisk)
|
|
348
|
+
let writeDir = "./";
|
|
349
|
+
if (config.writeToDisk) writeDir = mkdir("./data");
|
|
323
350
|
|
|
324
351
|
const writePaths = {
|
|
325
352
|
eventFiles: [path.join(writeDir, `events-${current}.${extension}`)],
|
|
@@ -347,19 +374,55 @@ function buildFileNames(config) {
|
|
|
347
374
|
return writePaths;
|
|
348
375
|
}
|
|
349
376
|
|
|
377
|
+
/**
|
|
378
|
+
* Generates a random timestamp with higher likelihood on peak days and typical business hours.
|
|
379
|
+
* @param {number} earliestTime - The earliest timestamp in Unix format.
|
|
380
|
+
* @param {number} latestTime - The latest timestamp in Unix format.
|
|
381
|
+
* @param {Array} peakDays - Array of Unix timestamps representing the start of peak days.
|
|
382
|
+
* @returns {number} - The generated event timestamp in Unix format.
|
|
383
|
+
*/
|
|
384
|
+
function customTimeDistribution(earliestTime, latestTime, peakDays) {
|
|
385
|
+
// Define business hours
|
|
386
|
+
const peakStartHour = 8; // 8 AM
|
|
387
|
+
const peakEndHour = 18; // 6 PM
|
|
388
|
+
const likelihoodOfPeakDay = chance.integer({ min: integer(5, 42), max: integer(43, 69) }); // Randomize likelihood with CHAOS!~~
|
|
389
|
+
|
|
390
|
+
// Select a day, with a preference for peak days
|
|
391
|
+
let selectedDay;
|
|
392
|
+
if (chance.bool({ likelihood: likelihoodOfPeakDay })) { // Randomized likelihood to pick a peak day
|
|
393
|
+
selectedDay = peakDays.length > 0 ? chance.pickone(peakDays) : integer(earliestTime, latestTime);
|
|
394
|
+
} else {
|
|
395
|
+
// Introduce minor peaks by allowing some events to still occur during business hours
|
|
396
|
+
selectedDay = chance.bool({ likelihood: 20 }) // 20% chance to simulate a minor peak on a non-peak day
|
|
397
|
+
? chance.pickone(peakDays)
|
|
398
|
+
: integer(earliestTime, latestTime);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Normalize selectedDay to the start of the day
|
|
402
|
+
selectedDay = dayjs.unix(selectedDay).startOf('day').unix();
|
|
403
|
+
|
|
404
|
+
// Generate a random time within business hours with a higher concentration in the middle of the period
|
|
405
|
+
const businessStart = dayjs.unix(selectedDay).hour(peakStartHour).minute(0).second(0).unix();
|
|
406
|
+
const businessEnd = dayjs.unix(selectedDay).hour(peakEndHour).minute(0).second(0).unix();
|
|
407
|
+
let eventTime;
|
|
408
|
+
if (selectedDay === peakDays[0]) {
|
|
409
|
+
// Use a skewed distribution for peak days
|
|
410
|
+
eventTime = chance.normal({ mean: (businessEnd + businessStart) / 2, dev: (businessEnd - businessStart) / 8 });
|
|
411
|
+
} else {
|
|
412
|
+
// For non-peak days, use a uniform distribution to add noise
|
|
413
|
+
eventTime = integer(businessStart, businessEnd);
|
|
414
|
+
}
|
|
415
|
+
eventTime = Math.min(Math.max(eventTime, businessStart), businessEnd); // Ensure time is within business hours
|
|
416
|
+
|
|
417
|
+
return eventTime;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
|
|
350
422
|
// this is for CLI
|
|
351
423
|
if (require.main === module) {
|
|
352
|
-
|
|
353
424
|
const args = cliParams();
|
|
354
|
-
const {
|
|
355
|
-
token,
|
|
356
|
-
seed,
|
|
357
|
-
format,
|
|
358
|
-
numDays,
|
|
359
|
-
numUsers,
|
|
360
|
-
numEvents,
|
|
361
|
-
region
|
|
362
|
-
} = args;
|
|
425
|
+
const { token, seed, format, numDays, numUsers, numEvents, region } = args;
|
|
363
426
|
const suppliedConfig = args._[0];
|
|
364
427
|
|
|
365
428
|
//if the user specifics an separate config file
|
|
@@ -381,25 +444,28 @@ if (require.main === module) {
|
|
|
381
444
|
if (numEvents) config.numEvents = numEvents;
|
|
382
445
|
if (region) config.region = region;
|
|
383
446
|
|
|
384
|
-
|
|
385
447
|
main(config)
|
|
386
448
|
.then((data) => {
|
|
387
449
|
console.log(`------------------SUMMARY------------------`);
|
|
388
450
|
const { events, groups, users } = data.import;
|
|
389
451
|
const files = data.files;
|
|
390
452
|
const folder = files.pop();
|
|
391
|
-
const groupBytes = groups.reduce((acc, group) => {
|
|
392
|
-
|
|
453
|
+
const groupBytes = groups.reduce((acc, group) => {
|
|
454
|
+
return acc + group.bytes;
|
|
455
|
+
}, 0);
|
|
456
|
+
const groupSuccess = groups.reduce((acc, group) => {
|
|
457
|
+
return acc + group.success;
|
|
458
|
+
}, 0);
|
|
393
459
|
const bytes = events.bytes + groupBytes + users.bytes;
|
|
394
460
|
const stats = {
|
|
395
461
|
events: comma(events.success || 0),
|
|
396
462
|
users: comma(users.success || 0),
|
|
397
463
|
groups: comma(groupSuccess || 0),
|
|
398
|
-
bytes: bytesHuman(bytes || 0)
|
|
464
|
+
bytes: bytesHuman(bytes || 0),
|
|
399
465
|
};
|
|
400
466
|
if (bytes > 0) console.table(stats);
|
|
401
467
|
console.log(`\nfiles written to ${folder}...`);
|
|
402
|
-
console.log("\t" + files.flat().join(
|
|
468
|
+
console.log("\t" + files.flat().join("\n\t"));
|
|
403
469
|
console.log(`\n------------------SUMMARY------------------\n\n\n`);
|
|
404
470
|
})
|
|
405
471
|
.catch((e) => {
|
|
@@ -409,13 +475,10 @@ if (require.main === module) {
|
|
|
409
475
|
debugger;
|
|
410
476
|
})
|
|
411
477
|
.finally(() => {
|
|
412
|
-
console.log(
|
|
478
|
+
console.log("have a wonderful day :)");
|
|
413
479
|
openFinder(path.resolve("./data"));
|
|
414
480
|
});
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
else {
|
|
481
|
+
} else {
|
|
419
482
|
module.exports = {
|
|
420
483
|
generate: main,
|
|
421
484
|
weightedRange,
|
|
@@ -429,9 +492,6 @@ else {
|
|
|
429
492
|
choose,
|
|
430
493
|
range,
|
|
431
494
|
exhaust,
|
|
432
|
-
openFinder
|
|
495
|
+
openFinder,
|
|
433
496
|
};
|
|
434
497
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|