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.
Files changed (3) hide show
  1. package/default.js +2 -2
  2. package/index.js +106 -46
  3. 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: 250000, //how many events
10
- numUsers: 1500, //how many users
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('./cli.js');
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 } = buildFileNames(config);
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) return { eventData, userProfilesData, scdTableData, groupProfilesData, lookupTableData };
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, { recordType: "event", ...importOpts });
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, { recordType: "user", ...importOpts });
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, { recordType: "group", ...importOpts });
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 { import: importResults, files: [eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, folder] };
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(distinct_id, earliestTime, events, superProps, groupKeys, isFirstEvent = false) {
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 = integer(earliestTime, now);
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 = null;
322
- if (config.writeToDisk) writeDir = mkdir('./data');
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) => { return acc + group.bytes; }, 0);
392
- const groupSuccess = groups.reduce((acc, group) => { return acc + group.success; }, 0);
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('\n\t'));
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('have a wonderful day :)');
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
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-mp-data",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "builds all mixpanel primitives for a given project",
5
5
  "main": "index.js",
6
6
  "scripts": {