make-mp-data 1.1.12 → 1.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/default.js +2 -0
- package/e2e.test.js +79 -9
- package/index.js +31 -25
- package/package.json +2 -2
- package/scripts/jsdoctest.js +5 -0
- package/types.d.ts +38 -1
- package/utils.js +19 -7
package/default.js
CHANGED
package/e2e.test.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/* eslint-disable no-undef */
|
|
4
4
|
/* eslint-disable no-debugger */
|
|
5
5
|
/* eslint-disable no-unused-vars */
|
|
6
|
-
const
|
|
6
|
+
const generate = require('./index.js');
|
|
7
7
|
require('dotenv').config();
|
|
8
8
|
const { execSync } = require("child_process");
|
|
9
9
|
const u = require('ak-tools');
|
|
@@ -36,14 +36,60 @@ describe('e2e', () => {
|
|
|
36
36
|
|
|
37
37
|
test('sends data to mixpanel', async () => {
|
|
38
38
|
console.log('NETWORK TEST');
|
|
39
|
-
const results = await generate({ writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it", token: testToken});
|
|
40
|
-
const {events, users, groups } = results.import;
|
|
39
|
+
const results = await generate({ writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it", token: testToken });
|
|
40
|
+
const { events, users, groups } = results.import;
|
|
41
41
|
expect(events.success).toBeGreaterThan(980);
|
|
42
42
|
expect(users.success).toBe(100);
|
|
43
|
-
expect(groups.length).toBe(0);
|
|
43
|
+
expect(groups.length).toBe(0);
|
|
44
|
+
}, timeout);
|
|
45
|
+
|
|
46
|
+
test('every record is valid', async () => {
|
|
47
|
+
console.log('VALIDATION TEST');
|
|
48
|
+
const results = await generate({ writeToDisk: false, numEvents: 10000, numUsers: 500 });
|
|
49
|
+
const { eventData, userProfilesData } = results;
|
|
50
|
+
const areEventsValid = eventData.every(validateEvent);
|
|
51
|
+
const areUsersValid = userProfilesData.every(validateUser);
|
|
52
|
+
|
|
53
|
+
const invalidEvents = eventData.filter(e => !validateEvent(e));
|
|
54
|
+
const invalidUsers = userProfilesData.filter(u => !validateUser(u));
|
|
44
55
|
|
|
56
|
+
expect(areEventsValid).toBe(true);
|
|
57
|
+
expect(areUsersValid).toBe(true);
|
|
58
|
+
}, timeout);
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('options + tweaks', () => {
|
|
64
|
+
test('creates sessionIds', async () => {
|
|
65
|
+
const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, sessionIds: true });
|
|
66
|
+
const { eventData } = results;
|
|
67
|
+
const sessionIds = eventData.map(a => a.$session_id).filter(a => a);
|
|
68
|
+
expect(sessionIds.length).toBe(eventData.length);
|
|
69
|
+
}, timeout);
|
|
70
|
+
|
|
71
|
+
test('no sessionIds', async () => {
|
|
72
|
+
const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, sessionIds: false });
|
|
73
|
+
const { eventData } = results;
|
|
74
|
+
const sessionIds = eventData.map(a => a.$session_id).filter(a => a);
|
|
75
|
+
expect(sessionIds.length).toBe(0);
|
|
76
|
+
}, timeout);
|
|
77
|
+
|
|
78
|
+
test('creates anonymousIds', async () => {
|
|
79
|
+
const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, anonIds: true });
|
|
80
|
+
const { eventData } = results;
|
|
81
|
+
const anonIds = eventData.map(a => a.$device_id).filter(a => a);
|
|
82
|
+
const userIds = eventData.map(a => a.$user_id).filter(a => a);
|
|
83
|
+
expect(anonIds.length).toBe(eventData.length);
|
|
84
|
+
expect(userIds.length).toBeLessThan(anonIds.length);
|
|
85
|
+
}, timeout);
|
|
86
|
+
|
|
87
|
+
test('no anonymousIds', async () => {
|
|
88
|
+
const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, anonIds: false });
|
|
89
|
+
const { eventData } = results;
|
|
90
|
+
const anonIds = eventData.map(a => a.$device_id).filter(a => a);
|
|
91
|
+
expect(anonIds.length).toBe(0);
|
|
45
92
|
}, timeout);
|
|
46
|
-
|
|
47
93
|
|
|
48
94
|
|
|
49
95
|
});
|
|
@@ -55,7 +101,31 @@ afterEach(() => {
|
|
|
55
101
|
});
|
|
56
102
|
|
|
57
103
|
afterAll(() => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
104
|
+
try {
|
|
105
|
+
console.log('clearing...');
|
|
106
|
+
execSync(`npm run prune`);
|
|
107
|
+
console.log('...files cleared 👍');
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
console.log('error clearing files');
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
//helpers
|
|
115
|
+
|
|
116
|
+
function validateEvent(event) {
|
|
117
|
+
if (!event.event) return false;
|
|
118
|
+
if (!event.$device_id && !event.$user_id) return false;
|
|
119
|
+
if (!event.time) return false;
|
|
120
|
+
if (!event.$insert_id) return false;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
function validateUser(user) {
|
|
126
|
+
if (!user.distinct_id) return false;
|
|
127
|
+
if (!user.$name) return false;
|
|
128
|
+
if (!user.$email) return false;
|
|
129
|
+
if (!user.$created) return false;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
package/index.js
CHANGED
|
@@ -35,7 +35,7 @@ const dayjs = require("dayjs");
|
|
|
35
35
|
const utc = require("dayjs/plugin/utc");
|
|
36
36
|
dayjs.extend(utc);
|
|
37
37
|
const cliParams = require("./cli.js");
|
|
38
|
-
const { makeName } = require('ak-tools');
|
|
38
|
+
const { makeName, md5 } = require('ak-tools');
|
|
39
39
|
|
|
40
40
|
Array.prototype.pickOne = pick;
|
|
41
41
|
const NOW = dayjs().unix();
|
|
@@ -78,6 +78,8 @@ async function main(config) {
|
|
|
78
78
|
groupKeys = [],
|
|
79
79
|
groupProps = {},
|
|
80
80
|
lookupTables = [],
|
|
81
|
+
anonIds = true,
|
|
82
|
+
sessionIds = true,
|
|
81
83
|
format = "csv",
|
|
82
84
|
token = null,
|
|
83
85
|
region = "US",
|
|
@@ -86,6 +88,7 @@ async function main(config) {
|
|
|
86
88
|
} = config;
|
|
87
89
|
VERBOSE = verbose;
|
|
88
90
|
config.simulationName = makeName();
|
|
91
|
+
global.config = config;
|
|
89
92
|
const uuidChance = new Chance(seed);
|
|
90
93
|
log(`------------------SETUP------------------`);
|
|
91
94
|
log(`\nyour data simulation will heretofore be known as: \n\n\t${config.simulationName.toUpperCase()}...\n`);
|
|
@@ -93,8 +96,8 @@ async function main(config) {
|
|
|
93
96
|
log(`------------------SETUP------------------`, "\n");
|
|
94
97
|
|
|
95
98
|
|
|
96
|
-
//the function which generates $distinct_id + $created, skewing towards the present
|
|
97
|
-
function
|
|
99
|
+
//the function which generates $distinct_id + $anonymous_ids, $session_ids, and $created, skewing towards the present
|
|
100
|
+
function generateUser() {
|
|
98
101
|
const distinct_id = uuidChance.guid();
|
|
99
102
|
let z = boxMullerRandom();
|
|
100
103
|
const skew = chance.normal({ mean: 10, dev: 3 });
|
|
@@ -136,8 +139,8 @@ async function main(config) {
|
|
|
136
139
|
log(`---------------SIMULATION----------------`, `\n\n`);
|
|
137
140
|
for (let i = 1; i < numUsers + 1; i++) {
|
|
138
141
|
progress("users", i);
|
|
139
|
-
const user =
|
|
140
|
-
const { distinct_id, $created, anonymousIds } = user;
|
|
142
|
+
const user = generateUser();
|
|
143
|
+
const { distinct_id, $created, anonymousIds, sessionIds } = user;
|
|
141
144
|
userProfilesData.push(makeProfile(userProps, user));
|
|
142
145
|
const mutations = chance.integer({ min: 1, max: 10 });
|
|
143
146
|
scdTableData.push(makeSCD(scdProps, distinct_id, mutations, $created));
|
|
@@ -150,6 +153,7 @@ async function main(config) {
|
|
|
150
153
|
makeEvent(
|
|
151
154
|
distinct_id,
|
|
152
155
|
anonymousIds,
|
|
156
|
+
sessionIds,
|
|
153
157
|
dayjs($created).unix(),
|
|
154
158
|
firstEvents,
|
|
155
159
|
superProps,
|
|
@@ -165,6 +169,7 @@ async function main(config) {
|
|
|
165
169
|
makeEvent(
|
|
166
170
|
distinct_id,
|
|
167
171
|
anonymousIds,
|
|
172
|
+
sessionIds,
|
|
168
173
|
dayjs($created).unix(),
|
|
169
174
|
weightedEvents,
|
|
170
175
|
superProps,
|
|
@@ -220,7 +225,7 @@ async function main(config) {
|
|
|
220
225
|
[lookupFiles, lookupTableData],
|
|
221
226
|
];
|
|
222
227
|
log("\n", `---------------SIMULATION----------------`, "\n");
|
|
223
|
-
|
|
228
|
+
|
|
224
229
|
if (!writeToDisk && !token) {
|
|
225
230
|
return {
|
|
226
231
|
eventData,
|
|
@@ -365,29 +370,41 @@ function makeSCD(props, distinct_id, mutations, $created) {
|
|
|
365
370
|
* creates a random event
|
|
366
371
|
* @param {string} distinct_id
|
|
367
372
|
* @param {string[]} anonymousIds
|
|
373
|
+
* @param {string[]} sessionIds
|
|
368
374
|
* @param {number} earliestTime
|
|
369
375
|
* @param {Object[]} events
|
|
370
376
|
* @param {Object} superProps
|
|
371
377
|
* @param {Object} groupKeys
|
|
372
378
|
* @param {Boolean} isFirstEvent=false
|
|
373
379
|
*/
|
|
374
|
-
function makeEvent(distinct_id, anonymousIds, earliestTime, events, superProps, groupKeys, isFirstEvent = false) {
|
|
375
|
-
|
|
380
|
+
function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events, superProps, groupKeys, isFirstEvent = false) {
|
|
376
381
|
let chosenEvent = events.pickOne();
|
|
377
|
-
|
|
382
|
+
|
|
383
|
+
//allow for a string shorthand
|
|
384
|
+
if (typeof chosenEvent === "string") {
|
|
378
385
|
chosenEvent = { event: chosenEvent, properties: {} };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
//event model
|
|
379
389
|
const event = {
|
|
380
390
|
event: chosenEvent.event,
|
|
381
|
-
$device_id: chance.pickone(anonymousIds), // always have a $device_id
|
|
382
391
|
$source: "AKsTimeSoup",
|
|
383
392
|
};
|
|
384
393
|
|
|
394
|
+
//event time
|
|
385
395
|
if (isFirstEvent) event.time = dayjs.unix(earliestTime).toISOString();
|
|
386
396
|
if (!isFirstEvent) event.time = AKsTimeSoup(earliestTime, NOW, PEAK_DAYS);
|
|
387
397
|
|
|
398
|
+
// anonymous and session ids
|
|
399
|
+
if (global?.config.anonIds) event.$device_id = chance.pickone(anonymousIds);
|
|
400
|
+
if (global?.config.sessionIds) event.$session_id = chance.pickone(sessionIds);
|
|
401
|
+
|
|
388
402
|
//sometimes have a $user_id
|
|
389
403
|
if (!isFirstEvent && chance.bool({ likelihood: 42 })) event.$user_id = distinct_id;
|
|
390
404
|
|
|
405
|
+
// ensure that there is a $user_id or $device_id
|
|
406
|
+
if (!event.$user_id && !event.$device_id) event.$user_id = distinct_id;
|
|
407
|
+
|
|
391
408
|
const props = { ...chosenEvent.properties, ...superProps };
|
|
392
409
|
|
|
393
410
|
//iterate through custom properties
|
|
@@ -408,6 +425,9 @@ function makeEvent(distinct_id, anonymousIds, earliestTime, events, superProps,
|
|
|
408
425
|
event[groupKey] = weightedRange(1, groupCardinality).pickOne();
|
|
409
426
|
}
|
|
410
427
|
|
|
428
|
+
//make $insert_id
|
|
429
|
+
event.$insert_id = md5(JSON.stringify(event));
|
|
430
|
+
|
|
411
431
|
return event;
|
|
412
432
|
}
|
|
413
433
|
|
|
@@ -555,21 +575,7 @@ if (require.main === module) {
|
|
|
555
575
|
openFinder(path.resolve("./data"));
|
|
556
576
|
});
|
|
557
577
|
} else {
|
|
558
|
-
module.exports =
|
|
559
|
-
generate: main,
|
|
560
|
-
weightedRange,
|
|
561
|
-
pick,
|
|
562
|
-
day,
|
|
563
|
-
integer,
|
|
564
|
-
makeProducts,
|
|
565
|
-
date,
|
|
566
|
-
progress,
|
|
567
|
-
person,
|
|
568
|
-
choose,
|
|
569
|
-
range,
|
|
570
|
-
exhaust,
|
|
571
|
-
openFinder,
|
|
572
|
-
};
|
|
578
|
+
module.exports = main;
|
|
573
579
|
}
|
|
574
580
|
|
|
575
581
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-mp-data",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.14",
|
|
4
4
|
"description": "builds all mixpanel primitives for a given project",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types.d.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/ak--47/make-mp-data#readme",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"ak-tools": "^1.0.
|
|
41
|
+
"ak-tools": "^1.0.53",
|
|
42
42
|
"chance": "^1.1.7",
|
|
43
43
|
"dayjs": "^1.11.10",
|
|
44
44
|
"mixpanel-import": "^2.5.5",
|
package/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
declare namespace main {
|
|
2
2
|
|
|
3
3
|
type primitives = string | number | boolean | Date | Object;
|
|
4
4
|
type valueValid = primitives | primitives[] | (() => primitives | primitives[]);
|
|
@@ -21,6 +21,8 @@ export interface Config {
|
|
|
21
21
|
writeToDisk?: boolean;
|
|
22
22
|
simulationName?: string;
|
|
23
23
|
verbose?: boolean;
|
|
24
|
+
anonIds?: boolean;
|
|
25
|
+
sessionIds?: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
interface EventConfig {
|
|
@@ -43,3 +45,38 @@ interface LookupTable {
|
|
|
43
45
|
[key?: string]: valueValid;
|
|
44
46
|
};
|
|
45
47
|
}
|
|
48
|
+
|
|
49
|
+
type Result = {
|
|
50
|
+
eventData: {
|
|
51
|
+
event: any;
|
|
52
|
+
$source: string;
|
|
53
|
+
}[];
|
|
54
|
+
userProfilesData: any[];
|
|
55
|
+
scdTableData: any[];
|
|
56
|
+
groupProfilesData: {
|
|
57
|
+
key: string;
|
|
58
|
+
data: any[];
|
|
59
|
+
}[];
|
|
60
|
+
lookupTableData: {
|
|
61
|
+
key: string;
|
|
62
|
+
data: any[];
|
|
63
|
+
}[];
|
|
64
|
+
import?: undefined;
|
|
65
|
+
files?: undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Mixpanel Data Generator
|
|
73
|
+
* model events, users, groups, and lookup tables (and SCD props!)
|
|
74
|
+
* @example
|
|
75
|
+
* const gen = require('make-mp-data')
|
|
76
|
+
* const dta = gen({writeToDisk: false})
|
|
77
|
+
*/
|
|
78
|
+
declare function main(
|
|
79
|
+
config: main.Config
|
|
80
|
+
): Promise<main.Result>;
|
|
81
|
+
export = main;
|
|
82
|
+
|
package/utils.js
CHANGED
|
@@ -231,18 +231,30 @@ function person(bornDaysAgo = 30) {
|
|
|
231
231
|
const avPath = gender === 'male' ? `/men/${randomAvatarNumber}.jpg` : `/women/${randomAvatarNumber}.jpg`;
|
|
232
232
|
const $avatar = avatarPrefix + avPath;
|
|
233
233
|
const $created = date(bornDaysAgo, true, null)();
|
|
234
|
+
|
|
235
|
+
//anon Ids
|
|
234
236
|
const anonymousIds = [];
|
|
235
237
|
const clusterSize = integer(2, 10);
|
|
236
238
|
for (let i = 0; i < clusterSize; i++) {
|
|
237
239
|
anonymousIds.push(uid(42));
|
|
238
240
|
}
|
|
239
241
|
|
|
242
|
+
//session Ids
|
|
243
|
+
const sessionIds = [];
|
|
244
|
+
const sessionSize = integer(5, 30);
|
|
245
|
+
for (let i = 0; i < sessionSize; i++) {
|
|
246
|
+
sessionIds.push([uid(5), uid(5), uid(5), uid(5)].join("-"));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
240
251
|
return {
|
|
241
252
|
$name,
|
|
242
253
|
$email,
|
|
243
254
|
$avatar,
|
|
244
255
|
$created,
|
|
245
|
-
anonymousIds
|
|
256
|
+
anonymousIds,
|
|
257
|
+
sessionIds
|
|
246
258
|
};
|
|
247
259
|
}
|
|
248
260
|
|
|
@@ -333,7 +345,7 @@ function generateName() {
|
|
|
333
345
|
"splintering", "crescendoing", "whirling", "bursting", "shining", "gushing", "emerging", "revealing",
|
|
334
346
|
"emerging", "unfolding", "unveiling", "emerging", "surrounding", "unveiling", "materializing", "revealing"
|
|
335
347
|
];
|
|
336
|
-
|
|
348
|
+
|
|
337
349
|
var adverbs = [
|
|
338
350
|
"gracefully", "softly", "smoothly", "gently", "tenderly", "quietly", "serenely", "peacefully",
|
|
339
351
|
"delicately", "effortlessly", "subtly", "tranquilly", "majestically", "silently", "calmly", "harmoniously",
|
|
@@ -343,16 +355,16 @@ function generateName() {
|
|
|
343
355
|
"seductively", "envelopingly", "ensnaringly", "entrancingly", "intoxicatingly", "irresistibly", "transcendentally",
|
|
344
356
|
"envelopingly", "rapturously", "intimately", "intensely", "tangibly", "vividly", "intensely", "deeply"
|
|
345
357
|
];
|
|
346
|
-
|
|
358
|
+
|
|
347
359
|
|
|
348
360
|
// ? http://stackoverflow.com/a/17516862/103058
|
|
349
|
-
var adj = adjs[Math.floor(Math.random() * adjs.length)];
|
|
361
|
+
var adj = adjs[Math.floor(Math.random() * adjs.length)];
|
|
350
362
|
var noun = nouns[Math.floor(Math.random() * nouns.length)];
|
|
351
363
|
var verb = verbs[Math.floor(Math.random() * verbs.length)];
|
|
352
364
|
var adverb = adverbs[Math.floor(Math.random() * adverbs.length)];
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return adj + '-' + noun + '-' + verb + '-' + adverb
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
return adj + '-' + noun + '-' + verb + '-' + adverb;
|
|
356
368
|
|
|
357
369
|
}
|
|
358
370
|
|