make-mp-data 1.3.2 โ 1.3.4
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/settings.json +1 -1
- package/cli.js +30 -17
- package/index.js +55 -27
- package/models/complex.js +24 -4
- package/models/foobar.js +110 -0
- package/package.json +4 -1
- package/tests/unit.test.js +15 -6
- package/timesoup.js +4 -4
- package/tsconfig.json +18 -0
- package/types.d.ts +126 -112
- package/utils.js +97 -26
package/.vscode/settings.json
CHANGED
package/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ by ak@mixpanel.com
|
|
|
15
15
|
|
|
16
16
|
function cliParams() {
|
|
17
17
|
console.log(hero);
|
|
18
|
+
// @ts-ignore
|
|
18
19
|
const args = yargs(process.argv.splice(2))
|
|
19
20
|
.scriptName("make-mp-data")
|
|
20
21
|
.usage(`\nusage:\nnpx $0 [dataModel.js] [options]
|
|
@@ -78,17 +79,7 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
|
|
|
78
79
|
describe: 'use complex data model (model all entities)',
|
|
79
80
|
alias: 'c',
|
|
80
81
|
type: 'boolean',
|
|
81
|
-
coerce:
|
|
82
|
-
if (typeof value === 'boolean') return value;
|
|
83
|
-
if (value === 'true') {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
if (value === 'false') {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
82
|
+
coerce: boolCoerce
|
|
92
83
|
})
|
|
93
84
|
.option("writeToDisk", {
|
|
94
85
|
demandOption: false,
|
|
@@ -96,13 +87,25 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
|
|
|
96
87
|
describe: 'write data to disk',
|
|
97
88
|
alias: 'w',
|
|
98
89
|
type: 'boolean',
|
|
99
|
-
coerce:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
90
|
+
coerce: boolCoerce
|
|
91
|
+
})
|
|
92
|
+
.option("sessionIds", {
|
|
93
|
+
demandOption: false,
|
|
94
|
+
default: false,
|
|
95
|
+
describe: 'create session ids in the data',
|
|
96
|
+
alias: 'sid',
|
|
97
|
+
type: 'boolean',
|
|
98
|
+
coerce: boolCoerce
|
|
99
|
+
})
|
|
100
|
+
.option("anonIds", {
|
|
101
|
+
demandOption: false,
|
|
102
|
+
default: false,
|
|
103
|
+
describe: 'create anonymous ids in the data',
|
|
104
|
+
alias: 'aid',
|
|
105
|
+
type: 'boolean',
|
|
106
|
+
coerce: boolCoerce
|
|
105
107
|
})
|
|
108
|
+
|
|
106
109
|
.help()
|
|
107
110
|
.wrap(null)
|
|
108
111
|
.argv;
|
|
@@ -115,4 +118,14 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
|
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
|
|
121
|
+
function boolCoerce(value, foo) {
|
|
122
|
+
if (typeof value === 'boolean') return value;
|
|
123
|
+
if (typeof value === 'string') {
|
|
124
|
+
return value.toLowerCase() === 'true';
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
118
131
|
module.exports = cliParams;
|
package/index.js
CHANGED
|
@@ -11,15 +11,13 @@ const mp = require("mixpanel-import");
|
|
|
11
11
|
const path = require("path");
|
|
12
12
|
const Chance = require("chance");
|
|
13
13
|
const chance = new Chance();
|
|
14
|
-
const {
|
|
15
|
-
const Papa = require("papaparse");
|
|
14
|
+
const { comma, bytesHuman, mkdir, makeName, md5, clone, tracker, uid } = require("ak-tools");
|
|
16
15
|
const u = require("./utils.js");
|
|
17
16
|
const AKsTimeSoup = require("./timesoup.js");
|
|
18
17
|
const dayjs = require("dayjs");
|
|
19
18
|
const utc = require("dayjs/plugin/utc");
|
|
20
19
|
dayjs.extend(utc);
|
|
21
20
|
const cliParams = require("./cli.js");
|
|
22
|
-
const { makeName, md5, clone, tracker, uid } = require('ak-tools');
|
|
23
21
|
const NOW = dayjs().unix();
|
|
24
22
|
let VERBOSE = false;
|
|
25
23
|
let isCLI = false;
|
|
@@ -77,7 +75,6 @@ async function main(config) {
|
|
|
77
75
|
numEvents,
|
|
78
76
|
numUsers,
|
|
79
77
|
numDays,
|
|
80
|
-
events,
|
|
81
78
|
anonIds,
|
|
82
79
|
sessionIds,
|
|
83
80
|
format,
|
|
@@ -89,7 +86,7 @@ async function main(config) {
|
|
|
89
86
|
});
|
|
90
87
|
log(`------------------SETUP------------------`);
|
|
91
88
|
log(`\nyour data simulation will heretofore be known as: \n\n\t${simulationName.toUpperCase()}...\n`);
|
|
92
|
-
log(`and your configuration is:\n\n`, JSON.stringify({ seed, numEvents, numUsers, numDays, format, token, region, writeToDisk }, null, 2));
|
|
89
|
+
log(`and your configuration is:\n\n`, JSON.stringify({ seed, numEvents, numUsers, numDays, format, token, region, writeToDisk, anonIds, sessionIds }, null, 2));
|
|
93
90
|
log(`------------------SETUP------------------`, "\n");
|
|
94
91
|
|
|
95
92
|
|
|
@@ -117,11 +114,13 @@ async function main(config) {
|
|
|
117
114
|
const weight = event.weight || 1;
|
|
118
115
|
for (let i = 0; i < weight; i++) {
|
|
119
116
|
|
|
117
|
+
// @ts-ignore
|
|
120
118
|
acc.push(event);
|
|
121
119
|
}
|
|
122
120
|
return acc;
|
|
123
121
|
}, [])
|
|
124
122
|
|
|
123
|
+
// @ts-ignore
|
|
125
124
|
.filter((e) => !e.isFirstEvent);
|
|
126
125
|
|
|
127
126
|
const firstEvents = events.filter((e) => e.isFirstEvent);
|
|
@@ -257,7 +256,6 @@ async function main(config) {
|
|
|
257
256
|
numEvents,
|
|
258
257
|
numUsers,
|
|
259
258
|
numDays,
|
|
260
|
-
events,
|
|
261
259
|
anonIds,
|
|
262
260
|
sessionIds,
|
|
263
261
|
format,
|
|
@@ -278,7 +276,8 @@ async function main(config) {
|
|
|
278
276
|
};
|
|
279
277
|
}
|
|
280
278
|
log(`-----------------WRITES------------------`, `\n\n`);
|
|
281
|
-
|
|
279
|
+
|
|
280
|
+
let writeFilePromises = [];
|
|
282
281
|
if (writeToDisk) {
|
|
283
282
|
if (verbose) log(`writing files... for ${simulationName}`);
|
|
284
283
|
loopFiles: for (const ENTITY of pairs) {
|
|
@@ -297,25 +296,16 @@ async function main(config) {
|
|
|
297
296
|
log(`\twriting ${path}`);
|
|
298
297
|
//if it's a lookup table, it's always a CSV
|
|
299
298
|
if (format === "csv" || path.includes("-LOOKUP.csv")) {
|
|
300
|
-
|
|
301
|
-
//papa parse needs eac nested field JSON stringified
|
|
302
|
-
TABLE.forEach((e) => {
|
|
303
|
-
for (const key in e) {
|
|
304
|
-
if (typeof e[key] === "object") e[key] = JSON.stringify(e[key]);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
const csv = Papa.unparse(TABLE, { columns });
|
|
309
|
-
await touch(path, csv);
|
|
299
|
+
writeFilePromises.push(u.streamCSV(path, TABLE));
|
|
310
300
|
}
|
|
311
301
|
else {
|
|
312
|
-
|
|
313
|
-
await touch(path, ndjson, false);
|
|
302
|
+
writeFilePromises.push(u.streamJSON(path, TABLE));
|
|
314
303
|
}
|
|
315
304
|
|
|
316
305
|
}
|
|
317
306
|
}
|
|
318
307
|
}
|
|
308
|
+
const fileWriteResults = await Promise.all(writeFilePromises);
|
|
319
309
|
|
|
320
310
|
const importResults = { events: {}, users: {}, groups: [] };
|
|
321
311
|
|
|
@@ -404,6 +394,10 @@ function makeProfile(props, defaults) {
|
|
|
404
394
|
...defaults,
|
|
405
395
|
};
|
|
406
396
|
|
|
397
|
+
// anonymous and session ids
|
|
398
|
+
if (!global.MP_SIMULATION_CONFIG?.anonIds) delete profile.anonymousIds;
|
|
399
|
+
if (!global.MP_SIMULATION_CONFIG?.sessionIds) delete profile.sessionIds;
|
|
400
|
+
|
|
407
401
|
for (const key in props) {
|
|
408
402
|
try {
|
|
409
403
|
profile[key] = u.choose(props[key]);
|
|
@@ -415,7 +409,7 @@ function makeProfile(props, defaults) {
|
|
|
415
409
|
return profile;
|
|
416
410
|
}
|
|
417
411
|
/**
|
|
418
|
-
* @param {import('./types.d.ts').
|
|
412
|
+
* @param {import('./types.d.ts').ValueValid} prop
|
|
419
413
|
* @param {string} scdKey
|
|
420
414
|
* @param {string} distinct_id
|
|
421
415
|
* @param {number} mutations
|
|
@@ -498,8 +492,11 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
|
|
|
498
492
|
for (const groupPair of groupKeys) {
|
|
499
493
|
const groupKey = groupPair[0];
|
|
500
494
|
const groupCardinality = groupPair[1];
|
|
495
|
+
const groupEvents = groupPair[2] || [];
|
|
501
496
|
|
|
502
|
-
|
|
497
|
+
// empty array for group events means all events
|
|
498
|
+
if (!groupEvents.length) event[groupKey] = u.pick(u.weightedRange(1, groupCardinality));
|
|
499
|
+
if (groupEvents.includes(event.event)) event[groupKey] = u.pick(u.weightedRange(1, groupCardinality));
|
|
503
500
|
}
|
|
504
501
|
|
|
505
502
|
//make $insert_id
|
|
@@ -509,18 +506,21 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
|
|
|
509
506
|
}
|
|
510
507
|
|
|
511
508
|
function buildFileNames(config) {
|
|
512
|
-
const { format = "csv", groupKeys = [], lookupTables = [] } = config;
|
|
513
|
-
|
|
509
|
+
const { format = "csv", groupKeys = [], lookupTables = [], m } = config;
|
|
510
|
+
let extension = "";
|
|
511
|
+
extension = format === "csv" ? "csv" : "json";
|
|
514
512
|
// const current = dayjs.utc().format("MM-DD-HH");
|
|
515
513
|
const simName = config.simulationName;
|
|
516
514
|
let writeDir = "./";
|
|
517
515
|
if (config.writeToDisk) writeDir = mkdir("./data");
|
|
516
|
+
if (typeof writeDir !== "string") throw new Error("writeDir must be a string");
|
|
517
|
+
if (typeof simName !== "string") throw new Error("simName must be a string");
|
|
518
518
|
|
|
519
519
|
const writePaths = {
|
|
520
520
|
eventFiles: [path.join(writeDir, `${simName}-EVENTS.${extension}`)],
|
|
521
521
|
userFiles: [path.join(writeDir, `${simName}-USERS.${extension}`)],
|
|
522
522
|
scdFiles: [],
|
|
523
|
-
mirrorFiles: [
|
|
523
|
+
mirrorFiles: [],
|
|
524
524
|
groupFiles: [],
|
|
525
525
|
lookupFiles: [],
|
|
526
526
|
folder: writeDir,
|
|
@@ -534,13 +534,16 @@ function buildFileNames(config) {
|
|
|
534
534
|
);
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
+
//add group files
|
|
537
538
|
for (const groupPair of groupKeys) {
|
|
538
539
|
const groupKey = groupPair[0];
|
|
540
|
+
|
|
539
541
|
writePaths.groupFiles.push(
|
|
540
542
|
path.join(writeDir, `${simName}-${groupKey}-GROUP.${extension}`)
|
|
541
543
|
);
|
|
542
544
|
}
|
|
543
545
|
|
|
546
|
+
//add lookup files
|
|
544
547
|
for (const lookupTable of lookupTables) {
|
|
545
548
|
const { key } = lookupTable;
|
|
546
549
|
writePaths.lookupFiles.push(
|
|
@@ -549,10 +552,25 @@ function buildFileNames(config) {
|
|
|
549
552
|
);
|
|
550
553
|
}
|
|
551
554
|
|
|
555
|
+
//add mirror files
|
|
556
|
+
const mirrorProps = config?.mirrorProps || {};
|
|
557
|
+
if (Object.keys(mirrorProps).length) {
|
|
558
|
+
writePaths.mirrorFiles.push(
|
|
559
|
+
path.join(writeDir, `${simName}-MIRROR.${extension}`)
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
552
563
|
return writePaths;
|
|
553
564
|
}
|
|
554
565
|
|
|
566
|
+
/** @typedef {import('./types').EnrichedArray} EnrichArray */
|
|
567
|
+
/** @typedef {import('./types').EnrichArrayOptions} EnrichArrayOptions */
|
|
555
568
|
|
|
569
|
+
/**
|
|
570
|
+
* @param {any[]} arr
|
|
571
|
+
* @param {EnrichArrayOptions} opts
|
|
572
|
+
* @returns {EnrichArray}}
|
|
573
|
+
*/
|
|
556
574
|
function enrichArray(arr = [], opts = {}) {
|
|
557
575
|
const { hook = a => a, type = "", ...rest } = opts;
|
|
558
576
|
|
|
@@ -560,9 +578,15 @@ function enrichArray(arr = [], opts = {}) {
|
|
|
560
578
|
return arr.push(hook(item, type, rest));
|
|
561
579
|
}
|
|
562
580
|
|
|
563
|
-
|
|
581
|
+
/** @type {EnrichArray} */
|
|
582
|
+
// @ts-ignore
|
|
583
|
+
const enrichedArray = arr;
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
enrichedArray.hPush = transformThenPush;
|
|
587
|
+
|
|
564
588
|
|
|
565
|
-
return
|
|
589
|
+
return enrichedArray;
|
|
566
590
|
};
|
|
567
591
|
|
|
568
592
|
|
|
@@ -571,7 +595,9 @@ function enrichArray(arr = [], opts = {}) {
|
|
|
571
595
|
if (require.main === module) {
|
|
572
596
|
isCLI = true;
|
|
573
597
|
const args = cliParams();
|
|
574
|
-
|
|
598
|
+
// @ts-ignore
|
|
599
|
+
const { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk, complex = false, sessionIds, anonIds } = args;
|
|
600
|
+
// @ts-ignore
|
|
575
601
|
const suppliedConfig = args._[0];
|
|
576
602
|
|
|
577
603
|
//if the user specifics an separate config file
|
|
@@ -605,6 +631,8 @@ if (require.main === module) {
|
|
|
605
631
|
if (region) config.region = region;
|
|
606
632
|
if (writeToDisk) config.writeToDisk = writeToDisk;
|
|
607
633
|
if (writeToDisk === 'false') config.writeToDisk = false;
|
|
634
|
+
if (sessionIds) config.sessionIds = sessionIds;
|
|
635
|
+
if (anonIds) config.anonIds = anonIds;
|
|
608
636
|
config.verbose = true;
|
|
609
637
|
|
|
610
638
|
main(config)
|
package/models/complex.js
CHANGED
|
@@ -63,6 +63,27 @@ const config = {
|
|
|
63
63
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
+
{
|
|
67
|
+
"event": "comment",
|
|
68
|
+
"weight": 2,
|
|
69
|
+
"properties": {
|
|
70
|
+
length: weightedRange(1, 500, 1000, .25),
|
|
71
|
+
video_id: weightedRange(1, 50000, 420000, 1.4),
|
|
72
|
+
has_replies: [true, false, false, false, false],
|
|
73
|
+
has_photo: [true, false, false, false, false],
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"event": "save video",
|
|
79
|
+
"weight": 4,
|
|
80
|
+
"properties": {
|
|
81
|
+
video_id: weightedRange(1, 50000, 420000, 1.4),
|
|
82
|
+
ui_control: ["toolbar", "menu", "keyboard"]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
},
|
|
66
87
|
{
|
|
67
88
|
"event": "view item",
|
|
68
89
|
"weight": 8,
|
|
@@ -143,8 +164,8 @@ const config = {
|
|
|
143
164
|
each pair represents a group_key and the number of profiles for that key
|
|
144
165
|
*/
|
|
145
166
|
groupKeys: [
|
|
146
|
-
['company_id', 500],
|
|
147
|
-
['room_id', 10000],
|
|
167
|
+
['company_id', 500, []],
|
|
168
|
+
['room_id', 10000, ["save video", "comment", "watch video"]],
|
|
148
169
|
|
|
149
170
|
],
|
|
150
171
|
groupProps: {
|
|
@@ -192,7 +213,6 @@ const config = {
|
|
|
192
213
|
copyright: ["all rights reserved", "creative commons", "creative commons", "public domain", "fair use"],
|
|
193
214
|
uploader_id: chance.guid.bind(chance),
|
|
194
215
|
"uploader influence": ["low", "low", "low", "medium", "medium", "high"],
|
|
195
|
-
rating: weightedRange(1, 5),
|
|
196
216
|
thumbs: weightedRange(0, 35),
|
|
197
217
|
rating: ["G", "PG", "PG-13", "R", "NC-17", "PG-13", "R", "NC-17", "R", "PG", "PG"]
|
|
198
218
|
}
|
|
@@ -286,7 +306,7 @@ function deviceAttributes(isMobile = false) {
|
|
|
286
306
|
let devices = ["desktop", "laptop", "desktop", "laptop", "desktop", "laptop", "other"];
|
|
287
307
|
if (isMobile) devices = [...devices, "mobile", "mobile", "mobile", "tablet"];
|
|
288
308
|
const device = chance.pickone(devices);
|
|
289
|
-
|
|
309
|
+
let oses = ["Windows", "macOS", "Windows", "macOS", "macOS", "Linux", "Windows", "macOS", "Windows", "macOS", "macOS", "TempleOS"];
|
|
290
310
|
if (isMobile) oses = [...oses, "iOS", "Android", "iOS", "Android"];
|
|
291
311
|
const os = chance.pickone(oses);
|
|
292
312
|
const browser = chance.pickone(["Chrome", "Firefox", "Safari", "Edge", "Opera", "IE", "Brave", "Vivaldi"]);
|
package/models/foobar.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the default configuration file for the data generator in SIMPLE mode
|
|
3
|
+
* notice how the config object is structured, and see it's type definition in ./types.d.ts
|
|
4
|
+
* feel free to modify this file to customize the data you generate
|
|
5
|
+
* see helper functions in utils.js for more ways to generate data
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const Chance = require('chance');
|
|
12
|
+
const chance = new Chance();
|
|
13
|
+
const dayjs = require("dayjs");
|
|
14
|
+
const utc = require("dayjs/plugin/utc");
|
|
15
|
+
dayjs.extend(utc);
|
|
16
|
+
const { uid, comma } = require('ak-tools');
|
|
17
|
+
const { weighList, weightedRange, date, integer } = require('../utils');
|
|
18
|
+
|
|
19
|
+
const itemCategories = ["Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden", "Pet", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art", "Food", "Appliances", "Office", "Wedding", "Software"];
|
|
20
|
+
|
|
21
|
+
const videoCategories = ["funny", "educational", "inspirational", "music", "news", "sports", "cooking", "DIY", "travel", "gaming"];
|
|
22
|
+
|
|
23
|
+
/** @type {import('../types').Config} */
|
|
24
|
+
const config = {
|
|
25
|
+
token: "",
|
|
26
|
+
seed: "foo bar",
|
|
27
|
+
numDays: 365, //how many days worth of data
|
|
28
|
+
numEvents: 10000000, //how many events
|
|
29
|
+
numUsers: 25000, //how many users
|
|
30
|
+
format: 'json', //csv or json
|
|
31
|
+
region: "US",
|
|
32
|
+
anonIds: true, //if true, anonymousIds are created for each user
|
|
33
|
+
sessionIds: false, //if true, sessionIds are created for each user
|
|
34
|
+
|
|
35
|
+
events: [
|
|
36
|
+
{
|
|
37
|
+
event: "foo",
|
|
38
|
+
weight: 10,
|
|
39
|
+
properties: {}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
event: "bar",
|
|
43
|
+
weight: 9,
|
|
44
|
+
properties: {}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
event: "baz",
|
|
48
|
+
weight: 8,
|
|
49
|
+
properties: {}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
event: "qux",
|
|
53
|
+
weight: 7,
|
|
54
|
+
properties: {}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
event: "garply",
|
|
58
|
+
weight: 6,
|
|
59
|
+
properties: {}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
event: "durtle",
|
|
63
|
+
weight: 5,
|
|
64
|
+
properties: {}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
event: "linny",
|
|
68
|
+
weight: 4,
|
|
69
|
+
properties: {}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
event: "fonk",
|
|
73
|
+
weight: 3,
|
|
74
|
+
properties: {}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
event: "crumn",
|
|
78
|
+
weight: 2,
|
|
79
|
+
properties: {}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
event: "yak",
|
|
83
|
+
weight: 1,
|
|
84
|
+
properties: {}
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
superProps: {
|
|
88
|
+
color: ["red", "orange", "yellow", "green", "blue", "indigo", "violet"],
|
|
89
|
+
number: integer,
|
|
90
|
+
|
|
91
|
+
},
|
|
92
|
+
userProps: {
|
|
93
|
+
title: chance.profession.bind(chance),
|
|
94
|
+
luckyNumber: weightedRange(42, 420),
|
|
95
|
+
spiritAnimal: ["duck", "dog", "otter", "penguin", "cat", "elephant", "lion", "cheetah", "giraffe", "zebra", "rhino", "hippo", "whale", "dolphin", "shark", "octopus", "squid", "jellyfish", "starfish", "seahorse", "crab", "lobster", "shrimp", "clam", "snail", "slug", "butterfly", "moth", "bee", "wasp", "ant", "beetle", "ladybug", "caterpillar", "centipede", "millipede", "scorpion", "spider", "tarantula", "tick", "mite", "mosquito", "fly", "dragonfly", "damselfly", "grasshopper", "cricket", "locust", "mantis", "cockroach", "termite", "praying mantis", "walking stick", "stick bug", "leaf insect", "lacewing", "aphid", "cicada", "thrips", "psyllid", "scale insect", "whitefly", "mealybug", "planthopper", "leafhopper", "treehopper", "flea", "louse", "bedbug", "flea beetle", "weevil", "longhorn beetle", "leaf beetle", "tiger beetle", "ground beetle", "lady beetle", "firefly", "click beetle", "rove beetle", "scarab beetle", "dung beetle", "stag beetle", "rhinoceros beetle", "hercules beetle", "goliath beetle", "jewel beetle", "tortoise beetle"]
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
scdProps: {},
|
|
99
|
+
mirrorProps: {},
|
|
100
|
+
groupKeys: [],
|
|
101
|
+
groupProps: {},
|
|
102
|
+
lookupTables: [],
|
|
103
|
+
hook: function (record, type, meta) {
|
|
104
|
+
return record;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
module.exports = config;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-mp-data",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.04",
|
|
4
4
|
"description": "builds all mixpanel primitives for a given project",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types.d.ts",
|
|
@@ -46,5 +46,8 @@
|
|
|
46
46
|
"mixpanel-import": "^2.5.51",
|
|
47
47
|
"papaparse": "^5.4.1",
|
|
48
48
|
"yargs": "^17.7.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/jest": "^29.5.12"
|
|
49
52
|
}
|
|
50
53
|
}
|
package/tests/unit.test.js
CHANGED
|
@@ -25,7 +25,7 @@ describe('timeSoup', () => {
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
const { applySkew, boxMullerRandom, choose, date, dates, day, exhaust, generateEmoji, getUniqueKeys, integer,
|
|
28
|
+
const { applySkew, boxMullerRandom, choose, date, dates, day, exhaust, generateEmoji, getUniqueKeys, integer, mapToRange, person, pick, range, weighList, weightedRange } = require('../utils');
|
|
29
29
|
|
|
30
30
|
describe('utils', () => {
|
|
31
31
|
|
|
@@ -124,8 +124,8 @@ describe('utils', () => {
|
|
|
124
124
|
const sd = 5;
|
|
125
125
|
const mappedValue = mapToRange(value, mean, sd);
|
|
126
126
|
expect(mappedValue).toBe(10);
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
});
|
|
128
|
+
|
|
129
129
|
|
|
130
130
|
|
|
131
131
|
test('exhaust: elements', () => {
|
|
@@ -184,15 +184,24 @@ describe('utils', () => {
|
|
|
184
184
|
test('emoji: works', () => {
|
|
185
185
|
const emojis = generateEmoji(5)();
|
|
186
186
|
expect(typeof emojis).toBe('string');
|
|
187
|
-
|
|
187
|
+
if (!Array.isArray(emojis)) {
|
|
188
|
+
expect(emojis.split(', ').length).toBeLessThanOrEqual(5);
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(emojis)) {
|
|
191
|
+
expect(emojis.length).toBeLessThanOrEqual(5);
|
|
192
|
+
}
|
|
188
193
|
});
|
|
189
194
|
|
|
190
195
|
test('emoji: length', () => {
|
|
191
196
|
const result = generateEmoji();
|
|
192
197
|
const emojis = result();
|
|
193
198
|
expect(typeof emojis).toBe('string');
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
if (!Array.isArray(emojis)) {
|
|
200
|
+
expect(emojis.split(', ').length).toBeLessThanOrEqual(10);
|
|
201
|
+
}
|
|
202
|
+
if (Array.isArray(emojis)) {
|
|
203
|
+
expect(emojis.length).toBeLessThanOrEqual(10);
|
|
204
|
+
}
|
|
196
205
|
|
|
197
206
|
});
|
|
198
207
|
|
package/timesoup.js
CHANGED
|
@@ -22,10 +22,10 @@ const PEAK_DAYS = [
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* essentially, a timestamp generator with a twist
|
|
25
|
-
* @param {number} earliestTime - The earliest timestamp in Unix format.
|
|
26
|
-
* @param {number} latestTime - The latest timestamp in Unix format.
|
|
27
|
-
* @param {Array} peakDays - Array of Unix timestamps representing the start of peak days.
|
|
28
|
-
* @returns {
|
|
25
|
+
* @param {number} [earliestTime] - The earliest timestamp in Unix format.
|
|
26
|
+
* @param {number} [latestTime] - The latest timestamp in Unix format.
|
|
27
|
+
* @param {Array} [peakDays] - Array of Unix timestamps representing the start of peak days.
|
|
28
|
+
* @returns {string} - The generated event timestamp in Unix format.
|
|
29
29
|
*/
|
|
30
30
|
function AKsTimeSoup(earliestTime, latestTime = NOW, peakDays = PEAK_DAYS) {
|
|
31
31
|
let chosenTime;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"checkJs": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"strict": false,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"resolveJsonModule": true
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"**/*.js"
|
|
14
|
+
],
|
|
15
|
+
"exclude": [
|
|
16
|
+
"node_modules"
|
|
17
|
+
]
|
|
18
|
+
}
|
package/types.d.ts
CHANGED
|
@@ -1,114 +1,128 @@
|
|
|
1
1
|
declare namespace main {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
anonIds?: boolean;
|
|
31
|
-
sessionIds?: boolean;
|
|
32
|
-
hook?: Hook;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type Hook = (record: any, type: string, meta: any) => any;
|
|
36
|
-
|
|
37
|
-
export interface EventConfig {
|
|
38
|
-
event?: string;
|
|
39
|
-
weight?: number;
|
|
40
|
-
properties?: Record<string, ValueValid>;
|
|
41
|
-
isFirstEvent?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface MirrorProps {
|
|
45
|
-
events: string[] | "*";
|
|
46
|
-
values: ValueValid[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface LookupTable {
|
|
50
|
-
key: string;
|
|
51
|
-
entries: number;
|
|
52
|
-
attributes: Record<string, ValueValid>;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface SCDTable {
|
|
56
|
-
distinct_id: string;
|
|
57
|
-
insertTime: string;
|
|
58
|
-
startTime: string;
|
|
59
|
-
[key: string]: ValueValid;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export type Result = {
|
|
63
|
-
eventData: EventData[];
|
|
64
|
-
userProfilesData: any[];
|
|
65
|
-
scdTableData: any[];
|
|
66
|
-
groupProfilesData: GroupProfilesData[];
|
|
67
|
-
lookupTableData: LookupTableData[];
|
|
68
|
-
import?: ImportResults;
|
|
69
|
-
files?: string[];
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export interface EventData {
|
|
73
|
-
event: string;
|
|
74
|
-
$source: string;
|
|
75
|
-
time: string;
|
|
76
|
-
$device_id?: string;
|
|
77
|
-
$session_id?: string;
|
|
78
|
-
$user_id?: string;
|
|
79
|
-
[key: string]: any;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface GroupProfilesData {
|
|
83
|
-
key: string;
|
|
84
|
-
data: any[];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface LookupTableData {
|
|
88
|
-
key: string;
|
|
89
|
-
data: any[];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface ImportResults {
|
|
93
|
-
events: ImportResult;
|
|
94
|
-
users: ImportResult;
|
|
95
|
-
groups: ImportResult[];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface ImportResult {
|
|
99
|
-
success: number;
|
|
100
|
-
bytes: number;
|
|
101
|
-
}
|
|
2
|
+
type Primitives = string | number | boolean | Date | Record<string, any>;
|
|
3
|
+
|
|
4
|
+
// Recursive type to handle functions returning functions that eventually return Primitives or arrays of Primitives
|
|
5
|
+
export type ValueValid = Primitives | ValueValid[] | (() => ValueValid);
|
|
6
|
+
|
|
7
|
+
// MAIN CONFIGURATION OBJECT
|
|
8
|
+
export interface Config {
|
|
9
|
+
token?: string;
|
|
10
|
+
seed?: string;
|
|
11
|
+
numDays?: number;
|
|
12
|
+
numEvents?: number;
|
|
13
|
+
numUsers?: number;
|
|
14
|
+
format?: "csv" | "json";
|
|
15
|
+
region?: "US" | "EU";
|
|
16
|
+
events?: EventConfig[];
|
|
17
|
+
superProps?: Record<string, ValueValid>;
|
|
18
|
+
userProps?: Record<string, ValueValid>;
|
|
19
|
+
scdProps?: Record<string, ValueValid>;
|
|
20
|
+
mirrorProps?: Record<string, MirrorProps>;
|
|
21
|
+
groupKeys?: [string, number][] | [string, number, string[]][];
|
|
22
|
+
groupProps?: Record<string, Record<string, ValueValid>>;
|
|
23
|
+
lookupTables?: LookupTable[];
|
|
24
|
+
writeToDisk?: boolean;
|
|
25
|
+
simulationName?: string;
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
anonIds?: boolean;
|
|
28
|
+
sessionIds?: boolean;
|
|
29
|
+
hook?: Hook<any>;
|
|
102
30
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
31
|
+
|
|
32
|
+
export type Hook<T> = (record: any, type: string, meta: any) => T;
|
|
33
|
+
|
|
34
|
+
export interface EnrichArrayOptions<T> {
|
|
35
|
+
hook?: Hook<T>;
|
|
36
|
+
type?: string;
|
|
37
|
+
[key: string]: any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface EnrichedArray<T> extends Array<T> {
|
|
41
|
+
hPush: (item: T) => number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface EventConfig {
|
|
45
|
+
event?: string;
|
|
46
|
+
weight?: number;
|
|
47
|
+
properties?: Record<string, ValueValid>;
|
|
48
|
+
isFirstEvent?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface MirrorProps {
|
|
52
|
+
events: string[] | "*";
|
|
53
|
+
values: ValueValid[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface LookupTable {
|
|
57
|
+
key: string;
|
|
58
|
+
entries: number;
|
|
59
|
+
attributes: Record<string, ValueValid>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SCDTable {
|
|
63
|
+
distinct_id: string;
|
|
64
|
+
insertTime: string;
|
|
65
|
+
startTime: string;
|
|
66
|
+
[key: string]: ValueValid;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type Result = {
|
|
70
|
+
eventData: EventData[];
|
|
71
|
+
userProfilesData: any[];
|
|
72
|
+
scdTableData: any[];
|
|
73
|
+
groupProfilesData: GroupProfilesData[];
|
|
74
|
+
lookupTableData: LookupTableData[];
|
|
75
|
+
import?: ImportResults;
|
|
76
|
+
files?: string[];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export interface EventData {
|
|
80
|
+
event: string;
|
|
81
|
+
$source: string;
|
|
82
|
+
time: string;
|
|
83
|
+
$device_id?: string;
|
|
84
|
+
$session_id?: string;
|
|
85
|
+
$user_id?: string;
|
|
86
|
+
[key: string]: any;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface GroupProfilesData {
|
|
90
|
+
key: string;
|
|
91
|
+
data: any[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface LookupTableData {
|
|
95
|
+
key: string;
|
|
96
|
+
data: any[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ImportResults {
|
|
100
|
+
events: ImportResult;
|
|
101
|
+
users: ImportResult;
|
|
102
|
+
groups: ImportResult[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ImportResult {
|
|
106
|
+
success: number;
|
|
107
|
+
bytes: number;
|
|
108
|
+
}
|
|
109
|
+
export interface Person {
|
|
110
|
+
$name: string;
|
|
111
|
+
$email: string;
|
|
112
|
+
$avatar: string;
|
|
113
|
+
$created: string | undefined;
|
|
114
|
+
anonymousIds: string[];
|
|
115
|
+
sessionIds: string[];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Mixpanel Data Generator
|
|
121
|
+
* model events, users, groups, and lookup tables (and SCD props!)
|
|
122
|
+
* @example
|
|
123
|
+
* const gen = require('make-mp-data')
|
|
124
|
+
* const dta = gen({writeToDisk: false})
|
|
125
|
+
*/
|
|
126
|
+
declare function main(config: main.Config): Promise<main.Result>;
|
|
127
|
+
|
|
128
|
+
export = main;
|
package/utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const Papa = require('papaparse');
|
|
1
3
|
const Chance = require('chance');
|
|
2
4
|
const chance = new Chance();
|
|
3
5
|
const readline = require('readline');
|
|
@@ -5,8 +7,10 @@ const { comma, uid } = require('ak-tools');
|
|
|
5
7
|
const { spawn } = require('child_process');
|
|
6
8
|
const dayjs = require('dayjs');
|
|
7
9
|
const utc = require('dayjs/plugin/utc');
|
|
10
|
+
|
|
8
11
|
dayjs.extend(utc);
|
|
9
12
|
|
|
13
|
+
|
|
10
14
|
function pick(items) {
|
|
11
15
|
if (!Array.isArray(items)) {
|
|
12
16
|
if (typeof items === 'function') {
|
|
@@ -57,10 +61,9 @@ function date(inTheLast = 30, isPast = true, format = 'YYYY-MM-DD') {
|
|
|
57
61
|
function dates(inTheLast = 30, numPairs = 5, format = 'YYYY-MM-DD') {
|
|
58
62
|
const pairs = [];
|
|
59
63
|
for (let i = 0; i < numPairs; i++) {
|
|
60
|
-
pairs.push([date(inTheLast, format), date(inTheLast, format)]);
|
|
64
|
+
pairs.push([date(inTheLast, true, format), date(inTheLast, true, format)]);
|
|
61
65
|
}
|
|
62
66
|
return pairs;
|
|
63
|
-
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
function day(start, end) {
|
|
@@ -96,11 +99,15 @@ function choose(value) {
|
|
|
96
99
|
return value;
|
|
97
100
|
}
|
|
98
101
|
|
|
102
|
+
if (typeof value === 'number') {
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
// If it's not a function or array, return it as is
|
|
100
107
|
return value;
|
|
101
108
|
}
|
|
102
109
|
catch (e) {
|
|
103
|
-
console.error(`\n\nerror on value: ${value};\n\n`,e, '\n\n');
|
|
110
|
+
console.error(`\n\nerror on value: ${value};\n\n`, e, '\n\n');
|
|
104
111
|
return '';
|
|
105
112
|
}
|
|
106
113
|
}
|
|
@@ -111,7 +118,7 @@ function exhaust(arr) {
|
|
|
111
118
|
};
|
|
112
119
|
|
|
113
120
|
|
|
114
|
-
function integer(min, max) {
|
|
121
|
+
function integer(min = 1, max = 100) {
|
|
115
122
|
if (min === max) {
|
|
116
123
|
return min;
|
|
117
124
|
}
|
|
@@ -178,22 +185,23 @@ function unOptimizedWeightedRange(min, max, size = 100, skew = 1) {
|
|
|
178
185
|
|
|
179
186
|
// optimized weighted range
|
|
180
187
|
function weightedRange(min, max, size = 100, skew = 1) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
188
|
+
const mean = (max + min) / 2;
|
|
189
|
+
const sd = (max - min) / 4;
|
|
190
|
+
const array = [];
|
|
191
|
+
while (array.length < size) {
|
|
192
|
+
const normalValue = boxMullerRandom();
|
|
193
|
+
const skewedValue = applySkew(normalValue, skew);
|
|
194
|
+
const mappedValue = mapToRange(skewedValue, mean, sd);
|
|
195
|
+
if (mappedValue >= min && mappedValue <= max) {
|
|
196
|
+
array.push(mappedValue);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return array;
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
|
|
196
203
|
function progress(thing, p) {
|
|
204
|
+
// @ts-ignore
|
|
197
205
|
readline.cursorTo(process.stdout, 0);
|
|
198
206
|
process.stdout.write(`${thing} processed ... ${comma(p)}`);
|
|
199
207
|
};
|
|
@@ -226,7 +234,12 @@ function getUniqueKeys(data) {
|
|
|
226
234
|
return Array.from(keysSet);
|
|
227
235
|
};
|
|
228
236
|
|
|
229
|
-
//
|
|
237
|
+
//
|
|
238
|
+
/**
|
|
239
|
+
* makes a random-sized array of emojis
|
|
240
|
+
* @param {number} max=10
|
|
241
|
+
* @param {boolean} array=false
|
|
242
|
+
*/
|
|
230
243
|
function generateEmoji(max = 10, array = false) {
|
|
231
244
|
return function () {
|
|
232
245
|
const emojis = ['๐', '๐', '๐', '๐', '๐', '๐', '๐ก', '๐ฑ', '๐ญ', '๐ด', '๐คข', '๐ค ', '๐คก', '๐ฝ', '๐ป', '๐ฉ', '๐บ', '๐น', '๐พ', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐', '๐', '๐', '๐', '๐', '๐ก', '๐ฑ', '๐ญ', '๐ด', '๐คข', '๐ค ', '๐คก', '๐ฝ', '๐ป', '๐ฉ', '๐บ', '๐น', '๐พ', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐ฟ', '๐ฆ', '๐ง', '๐จ', '๐ฉ', '๐ด', '๐ต', '๐ถ', '๐ง', '๐ฎ', '๐ท', '๐', '๐ต', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐ฉโ๐พ', '๐จโ๐พ', '๐ฉโ๐ณ', '๐จโ๐ณ', '๐ฉโ๐', '๐จโ๐', '๐ฉโ๐ค', '๐จโ๐ค', '๐ฉโ๐ซ', '๐จโ๐ซ', '๐ฉโ๐ญ', '๐จโ๐ญ', '๐ฉโ๐ป', '๐จโ๐ป', '๐ฉโ๐ผ', '๐จโ๐ผ', '๐ฉโ๐ง', '๐จโ๐ง', '๐ฉโ๐ฌ', '๐จโ๐ฌ', '๐ฉโ๐จ', '๐จโ๐จ', '๐ฉโ๐', '๐จโ๐', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐ฉโ๐', '๐จโ๐', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐คถ', '๐
', '๐ธ', '๐คด', '๐ฐ', '๐คต', '๐ผ', '๐คฐ', '๐', '๐', '๐
', '๐', '๐', '๐คฆ', '๐คท', '๐', '๐', '๐', '๐', '๐ด', '๐', '๐บ', '๐ถ', '๐', '๐คฒ', '๐', '๐', '๐', '๐ค', '๐', '๐', '๐', 'โ', '๐ค', '๐ค', '๐ค', 'โ๏ธ', '๐ค', '๐ค', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', 'โ', '๐ค', '๐', '๐', '๐', '๐ค', '๐ช', '๐', 'โ๏ธ', '๐คณ', '๐
', '๐', '๐', '๐ฃ', '๐', '๐', '๐ง ', '๐
', '๐', '๐', '๐', '๐ถ', '๐', '๐', '๐', '๐งฃ', '๐งค', '๐งฅ', '๐งฆ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ ', '๐ก', '๐ข', '๐', '๐', '๐ฉ', '๐', '๐งข', 'โ', '๐ฟ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ข', '๐ฃ', '๐ฏ', '๐', '๐', '๐ผ', '๐ต', '๐ถ', '๐', '๐', '๐', '๐ค', '๐ง', '๐ป', '๐ท', '๐ธ', '๐น', '๐บ', '๐ป', '๐ฅ', '๐ฑ', '๐ฒ', '๐ป', '๐ฅ', '๐จ', '๐ฑ', '๐ฒ', '๐น', '๐', '๐ฝ', '๐พ', '๐ฟ', '๐', '๐ผ', '๐ท', '๐ธ', '๐น', '๐ฅ', '๐ฝ', '๐', '๐', 'โ๏ธ', '๐', '๐ ', '๐บ', '๐ป', '๐', '๐ก', '๐', '๐', '๐ฌ', '๐ญ', '๐ก', '๐ก', '๐ฆ', '๐ฎ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฐ', '๐', '๐', '๐', '๐ท', '๐ฐ', '๐ด', '๐ต', '๐ถ', '๐ท', '๐ธ', '๐ณ', '๐งพ', '๐น', '๐ฑ', '๐ฒ', 'โ๏ธ', '๐ง', '๐จ', '๐ฉ', '๐ค', '๐ฅ', '๐ฆ', '๐ซ', '๐ช', '๐ฌ', '๐ญ', '๐ฎ', '๐ณ', 'โ๏ธ', 'โ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐ผ', '๐', '๐', '๐', '๐
', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐จ', 'โ', 'โ', '๐ ', '๐ก', 'โ๏ธ', '๐ซ', '๐น', '๐ก', '๐ง', '๐ฉ', 'โ๏ธ', '๐', 'โ๏ธ', '๐', 'โ', '๐งฐ', '๐งฒ', 'โ๏ธ', '๐งช', '๐งซ', '๐งฌ', '๐ฌ', '๐ญ', '๐ก', '๐', '๐', '๐', '๐', '๐ช', '๐ฝ', '๐ฟ', '๐', '๐งด', '๐งท', '๐งน', '๐งบ', '๐งป', '๐งผ', '๐งฝ', '๐งฏ', '๐ฌ', 'โฐ๏ธ', 'โฑ๏ธ', '๐ฟ', '๐บ', '๐งฑ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฎ', '๐', '๐งง', 'โ๏ธ', '๐ฉ', '๐จ', '๐ง'];
|
|
@@ -241,10 +254,17 @@ function generateEmoji(max = 10, array = false) {
|
|
|
241
254
|
};
|
|
242
255
|
};
|
|
243
256
|
|
|
257
|
+
/** @typedef {import('./types').Person} Person */
|
|
244
258
|
|
|
259
|
+
/**
|
|
260
|
+
* @param {number} bornDaysAgo=30
|
|
261
|
+
* @return {Person}
|
|
262
|
+
*/
|
|
245
263
|
function person(bornDaysAgo = 30) {
|
|
246
264
|
//names and photos
|
|
247
|
-
|
|
265
|
+
let gender = chance.pickone(['male', 'female']);
|
|
266
|
+
if (!gender) gender = "female";
|
|
267
|
+
// @ts-ignore
|
|
248
268
|
const first = chance.first({ gender });
|
|
249
269
|
const last = chance.last();
|
|
250
270
|
const $name = `${first} ${last}`;
|
|
@@ -256,33 +276,35 @@ function person(bornDaysAgo = 30) {
|
|
|
256
276
|
});
|
|
257
277
|
const avPath = gender === 'male' ? `/men/${randomAvatarNumber}.jpg` : `/women/${randomAvatarNumber}.jpg`;
|
|
258
278
|
const $avatar = avatarPrefix + avPath;
|
|
259
|
-
const $created = date(bornDaysAgo, true
|
|
279
|
+
const $created = date(bornDaysAgo, true)();
|
|
260
280
|
|
|
281
|
+
/** @type {Person} */
|
|
261
282
|
const user = {
|
|
262
283
|
$name,
|
|
263
284
|
$email,
|
|
264
285
|
$avatar,
|
|
265
286
|
$created,
|
|
287
|
+
anonymousIds: [],
|
|
288
|
+
sessionIds: []
|
|
266
289
|
};
|
|
267
290
|
|
|
268
291
|
//anon Ids
|
|
269
292
|
if (global.MP_SIMULATION_CONFIG?.anonIds) {
|
|
270
|
-
const anonymousIds = [];
|
|
271
293
|
const clusterSize = integer(2, 10);
|
|
272
294
|
for (let i = 0; i < clusterSize; i++) {
|
|
273
|
-
|
|
295
|
+
const anonId = uid(42);
|
|
296
|
+
user.anonymousIds.push(anonId);
|
|
274
297
|
}
|
|
275
|
-
|
|
298
|
+
|
|
276
299
|
}
|
|
277
300
|
|
|
278
301
|
//session Ids
|
|
279
302
|
if (global.MP_SIMULATION_CONFIG?.sessionIds) {
|
|
280
|
-
const sessionIds = [];
|
|
281
303
|
const sessionSize = integer(5, 30);
|
|
282
304
|
for (let i = 0; i < sessionSize; i++) {
|
|
283
|
-
|
|
305
|
+
const sessionId = [uid(5), uid(5), uid(5), uid(5)].join("-");
|
|
306
|
+
user.sessionIds.push(sessionId);
|
|
284
307
|
}
|
|
285
|
-
user.sessionIds = sessionIds;
|
|
286
308
|
}
|
|
287
309
|
|
|
288
310
|
return user;
|
|
@@ -316,6 +338,51 @@ function weighList(items, mostChosenIndex) {
|
|
|
316
338
|
};
|
|
317
339
|
}
|
|
318
340
|
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
function streamJSON(path, data) {
|
|
345
|
+
return new Promise((resolve, reject) => {
|
|
346
|
+
const writeStream = fs.createWriteStream(path, { encoding: 'utf8' });
|
|
347
|
+
data.forEach(item => {
|
|
348
|
+
writeStream.write(JSON.stringify(item) + '\n');
|
|
349
|
+
});
|
|
350
|
+
writeStream.end();
|
|
351
|
+
writeStream.on('finish', () => {
|
|
352
|
+
resolve(path);
|
|
353
|
+
});
|
|
354
|
+
writeStream.on('error', reject);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function streamCSV(path, data) {
|
|
359
|
+
return new Promise((resolve, reject) => {
|
|
360
|
+
const writeStream = fs.createWriteStream(path, { encoding: 'utf8' });
|
|
361
|
+
// Extract all unique keys from the data array
|
|
362
|
+
const columns = getUniqueKeys(data); // Assuming getUniqueKeys properly retrieves all keys
|
|
363
|
+
|
|
364
|
+
// Stream the header
|
|
365
|
+
writeStream.write(columns.join(',') + '\n');
|
|
366
|
+
|
|
367
|
+
// Stream each data row
|
|
368
|
+
data.forEach(item => {
|
|
369
|
+
for (const key in item) {
|
|
370
|
+
// Ensure all nested objects are properly stringified
|
|
371
|
+
if (typeof item[key] === "object") item[key] = JSON.stringify(item[key]);
|
|
372
|
+
}
|
|
373
|
+
const row = columns.map(col => item[col] ? `"${item[col].toString().replace(/"/g, '""')}"` : "").join(',');
|
|
374
|
+
writeStream.write(row + '\n');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
writeStream.end();
|
|
378
|
+
writeStream.on('finish', () => {
|
|
379
|
+
resolve(path);
|
|
380
|
+
});
|
|
381
|
+
writeStream.on('error', reject);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
319
386
|
module.exports = {
|
|
320
387
|
pick,
|
|
321
388
|
date,
|
|
@@ -335,5 +402,9 @@ module.exports = {
|
|
|
335
402
|
getUniqueKeys,
|
|
336
403
|
generateEmoji,
|
|
337
404
|
person,
|
|
338
|
-
weighList
|
|
405
|
+
weighList,
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
streamJSON,
|
|
409
|
+
streamCSV
|
|
339
410
|
};
|