make-mp-data 1.3.3 โ 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 -25
- 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;
|
|
@@ -88,7 +86,7 @@ async function main(config) {
|
|
|
88
86
|
});
|
|
89
87
|
log(`------------------SETUP------------------`);
|
|
90
88
|
log(`\nyour data simulation will heretofore be known as: \n\n\t${simulationName.toUpperCase()}...\n`);
|
|
91
|
-
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));
|
|
92
90
|
log(`------------------SETUP------------------`, "\n");
|
|
93
91
|
|
|
94
92
|
|
|
@@ -116,11 +114,13 @@ async function main(config) {
|
|
|
116
114
|
const weight = event.weight || 1;
|
|
117
115
|
for (let i = 0; i < weight; i++) {
|
|
118
116
|
|
|
117
|
+
// @ts-ignore
|
|
119
118
|
acc.push(event);
|
|
120
119
|
}
|
|
121
120
|
return acc;
|
|
122
121
|
}, [])
|
|
123
122
|
|
|
123
|
+
// @ts-ignore
|
|
124
124
|
.filter((e) => !e.isFirstEvent);
|
|
125
125
|
|
|
126
126
|
const firstEvents = events.filter((e) => e.isFirstEvent);
|
|
@@ -276,7 +276,8 @@ async function main(config) {
|
|
|
276
276
|
};
|
|
277
277
|
}
|
|
278
278
|
log(`-----------------WRITES------------------`, `\n\n`);
|
|
279
|
-
|
|
279
|
+
|
|
280
|
+
let writeFilePromises = [];
|
|
280
281
|
if (writeToDisk) {
|
|
281
282
|
if (verbose) log(`writing files... for ${simulationName}`);
|
|
282
283
|
loopFiles: for (const ENTITY of pairs) {
|
|
@@ -295,25 +296,16 @@ async function main(config) {
|
|
|
295
296
|
log(`\twriting ${path}`);
|
|
296
297
|
//if it's a lookup table, it's always a CSV
|
|
297
298
|
if (format === "csv" || path.includes("-LOOKUP.csv")) {
|
|
298
|
-
|
|
299
|
-
//papa parse needs eac nested field JSON stringified
|
|
300
|
-
TABLE.forEach((e) => {
|
|
301
|
-
for (const key in e) {
|
|
302
|
-
if (typeof e[key] === "object") e[key] = JSON.stringify(e[key]);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
const csv = Papa.unparse(TABLE, { columns });
|
|
307
|
-
await touch(path, csv);
|
|
299
|
+
writeFilePromises.push(u.streamCSV(path, TABLE));
|
|
308
300
|
}
|
|
309
301
|
else {
|
|
310
|
-
|
|
311
|
-
await touch(path, ndjson, false);
|
|
302
|
+
writeFilePromises.push(u.streamJSON(path, TABLE));
|
|
312
303
|
}
|
|
313
304
|
|
|
314
305
|
}
|
|
315
306
|
}
|
|
316
307
|
}
|
|
308
|
+
const fileWriteResults = await Promise.all(writeFilePromises);
|
|
317
309
|
|
|
318
310
|
const importResults = { events: {}, users: {}, groups: [] };
|
|
319
311
|
|
|
@@ -402,6 +394,10 @@ function makeProfile(props, defaults) {
|
|
|
402
394
|
...defaults,
|
|
403
395
|
};
|
|
404
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
|
+
|
|
405
401
|
for (const key in props) {
|
|
406
402
|
try {
|
|
407
403
|
profile[key] = u.choose(props[key]);
|
|
@@ -413,7 +409,7 @@ function makeProfile(props, defaults) {
|
|
|
413
409
|
return profile;
|
|
414
410
|
}
|
|
415
411
|
/**
|
|
416
|
-
* @param {import('./types.d.ts').
|
|
412
|
+
* @param {import('./types.d.ts').ValueValid} prop
|
|
417
413
|
* @param {string} scdKey
|
|
418
414
|
* @param {string} distinct_id
|
|
419
415
|
* @param {number} mutations
|
|
@@ -496,8 +492,11 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
|
|
|
496
492
|
for (const groupPair of groupKeys) {
|
|
497
493
|
const groupKey = groupPair[0];
|
|
498
494
|
const groupCardinality = groupPair[1];
|
|
495
|
+
const groupEvents = groupPair[2] || [];
|
|
499
496
|
|
|
500
|
-
|
|
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));
|
|
501
500
|
}
|
|
502
501
|
|
|
503
502
|
//make $insert_id
|
|
@@ -507,18 +506,21 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
|
|
|
507
506
|
}
|
|
508
507
|
|
|
509
508
|
function buildFileNames(config) {
|
|
510
|
-
const { format = "csv", groupKeys = [], lookupTables = [] } = config;
|
|
511
|
-
|
|
509
|
+
const { format = "csv", groupKeys = [], lookupTables = [], m } = config;
|
|
510
|
+
let extension = "";
|
|
511
|
+
extension = format === "csv" ? "csv" : "json";
|
|
512
512
|
// const current = dayjs.utc().format("MM-DD-HH");
|
|
513
513
|
const simName = config.simulationName;
|
|
514
514
|
let writeDir = "./";
|
|
515
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");
|
|
516
518
|
|
|
517
519
|
const writePaths = {
|
|
518
520
|
eventFiles: [path.join(writeDir, `${simName}-EVENTS.${extension}`)],
|
|
519
521
|
userFiles: [path.join(writeDir, `${simName}-USERS.${extension}`)],
|
|
520
522
|
scdFiles: [],
|
|
521
|
-
mirrorFiles: [
|
|
523
|
+
mirrorFiles: [],
|
|
522
524
|
groupFiles: [],
|
|
523
525
|
lookupFiles: [],
|
|
524
526
|
folder: writeDir,
|
|
@@ -532,13 +534,16 @@ function buildFileNames(config) {
|
|
|
532
534
|
);
|
|
533
535
|
}
|
|
534
536
|
|
|
537
|
+
//add group files
|
|
535
538
|
for (const groupPair of groupKeys) {
|
|
536
539
|
const groupKey = groupPair[0];
|
|
540
|
+
|
|
537
541
|
writePaths.groupFiles.push(
|
|
538
542
|
path.join(writeDir, `${simName}-${groupKey}-GROUP.${extension}`)
|
|
539
543
|
);
|
|
540
544
|
}
|
|
541
545
|
|
|
546
|
+
//add lookup files
|
|
542
547
|
for (const lookupTable of lookupTables) {
|
|
543
548
|
const { key } = lookupTable;
|
|
544
549
|
writePaths.lookupFiles.push(
|
|
@@ -547,10 +552,25 @@ function buildFileNames(config) {
|
|
|
547
552
|
);
|
|
548
553
|
}
|
|
549
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
|
+
|
|
550
563
|
return writePaths;
|
|
551
564
|
}
|
|
552
565
|
|
|
566
|
+
/** @typedef {import('./types').EnrichedArray} EnrichArray */
|
|
567
|
+
/** @typedef {import('./types').EnrichArrayOptions} EnrichArrayOptions */
|
|
553
568
|
|
|
569
|
+
/**
|
|
570
|
+
* @param {any[]} arr
|
|
571
|
+
* @param {EnrichArrayOptions} opts
|
|
572
|
+
* @returns {EnrichArray}}
|
|
573
|
+
*/
|
|
554
574
|
function enrichArray(arr = [], opts = {}) {
|
|
555
575
|
const { hook = a => a, type = "", ...rest } = opts;
|
|
556
576
|
|
|
@@ -558,9 +578,15 @@ function enrichArray(arr = [], opts = {}) {
|
|
|
558
578
|
return arr.push(hook(item, type, rest));
|
|
559
579
|
}
|
|
560
580
|
|
|
561
|
-
|
|
581
|
+
/** @type {EnrichArray} */
|
|
582
|
+
// @ts-ignore
|
|
583
|
+
const enrichedArray = arr;
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
enrichedArray.hPush = transformThenPush;
|
|
587
|
+
|
|
562
588
|
|
|
563
|
-
return
|
|
589
|
+
return enrichedArray;
|
|
564
590
|
};
|
|
565
591
|
|
|
566
592
|
|
|
@@ -569,7 +595,9 @@ function enrichArray(arr = [], opts = {}) {
|
|
|
569
595
|
if (require.main === module) {
|
|
570
596
|
isCLI = true;
|
|
571
597
|
const args = cliParams();
|
|
572
|
-
|
|
598
|
+
// @ts-ignore
|
|
599
|
+
const { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk, complex = false, sessionIds, anonIds } = args;
|
|
600
|
+
// @ts-ignore
|
|
573
601
|
const suppliedConfig = args._[0];
|
|
574
602
|
|
|
575
603
|
//if the user specifics an separate config file
|
|
@@ -603,6 +631,8 @@ if (require.main === module) {
|
|
|
603
631
|
if (region) config.region = region;
|
|
604
632
|
if (writeToDisk) config.writeToDisk = writeToDisk;
|
|
605
633
|
if (writeToDisk === 'false') config.writeToDisk = false;
|
|
634
|
+
if (sessionIds) config.sessionIds = sessionIds;
|
|
635
|
+
if (anonIds) config.anonIds = anonIds;
|
|
606
636
|
config.verbose = true;
|
|
607
637
|
|
|
608
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
|
};
|