make-mp-data 1.4.1 → 1.4.2
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/defaults.js +799 -11487
- package/index.js +184 -78
- package/package.json +3 -3
- package/schemas/simple.js +10 -0
- package/scratch.mjs +3 -2
- package/tests/unit.test.js +5 -4
- package/types.d.ts +15 -2
- package/utils.js +72 -13
package/index.js
CHANGED
|
@@ -6,32 +6,43 @@ by AK
|
|
|
6
6
|
ak@mixpanel.com
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
//todo: ads-data
|
|
10
|
+
//todo: cart analysis
|
|
9
11
|
//todo: churn ... is churnFunnel, possible to return, etc
|
|
10
12
|
//todo: fixedTimeFunnel? if set this funnel will occur for all users at the same time ['cart charged', 'charge complete']
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
/** @typedef {import('./types.d.ts').Config} Config */
|
|
15
|
+
/** @typedef {import('./types.d.ts').EventConfig} EventConfig */
|
|
16
|
+
/** @typedef {import('./types.d.ts').Funnel} Funnel */
|
|
17
|
+
/** @typedef {import('./types.d.ts').Person} Person */
|
|
18
|
+
/** @typedef {import('./types.d.ts').SCDTableRow} SCDTableRow */
|
|
19
|
+
/** @typedef {import('./types.d.ts').UserProfile} UserProfile */
|
|
20
|
+
/** @typedef {import('./types.d.ts').EventSpec} EventSpec */
|
|
21
|
+
|
|
15
22
|
const dayjs = require("dayjs");
|
|
16
23
|
const utc = require("dayjs/plugin/utc");
|
|
17
24
|
dayjs.extend(utc);
|
|
18
25
|
const NOW = dayjs('2024-02-02').unix(); //this is a FIXED POINT and we will shift it later
|
|
19
26
|
global.NOW = NOW;
|
|
20
|
-
|
|
27
|
+
|
|
28
|
+
const os = require("os");
|
|
21
29
|
const path = require("path");
|
|
22
30
|
const { comma, bytesHuman, makeName, md5, clone, tracker, uid } = require("ak-tools");
|
|
23
31
|
const { generateLineChart } = require('./chart.js');
|
|
24
32
|
const { version } = require('./package.json');
|
|
25
|
-
const
|
|
33
|
+
const mp = require("mixpanel-import");
|
|
26
34
|
const metrics = tracker("make-mp-data", "db99eb8f67ae50949a13c27cacf57d41", os.userInfo().username);
|
|
27
35
|
|
|
28
|
-
|
|
29
36
|
const u = require("./utils.js");
|
|
30
|
-
const
|
|
37
|
+
const getCliParams = require("./cli.js");
|
|
38
|
+
const { campaigns, devices, locations } = require('./defaults.js');
|
|
31
39
|
|
|
32
40
|
let VERBOSE = false;
|
|
33
41
|
let isCLI = false;
|
|
42
|
+
/** @type {Config} */
|
|
34
43
|
let CONFIG;
|
|
44
|
+
|
|
45
|
+
let DEFAULTS;
|
|
35
46
|
require('dotenv').config();
|
|
36
47
|
|
|
37
48
|
|
|
@@ -40,25 +51,20 @@ function track(name, props, ...rest) {
|
|
|
40
51
|
metrics(name, props, ...rest);
|
|
41
52
|
}
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
/** @typedef {import('./types.d.ts').EventConfig} EventConfig */
|
|
45
|
-
/** @typedef {import('./types.d.ts').Funnel} Funnel */
|
|
46
|
-
/** @typedef {import('./types.d.ts').Person} Person */
|
|
47
|
-
/** @typedef {import('./types.d.ts').SCDTableRow} SCDTableRow */
|
|
48
|
-
/** @typedef {import('./types.d.ts').UserProfile} UserProfile */
|
|
49
|
-
/** @typedef {import('./types.d.ts').EventSpec} EventSpec */
|
|
54
|
+
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
57
|
* generates fake mixpanel data
|
|
53
58
|
* @param {Config} config
|
|
54
59
|
*/
|
|
55
60
|
async function main(config) {
|
|
56
|
-
|
|
61
|
+
|
|
62
|
+
//seed the random number generator
|
|
63
|
+
// ^ this is critical; same seed = same data; seed can be passed in as an env var or in the config
|
|
57
64
|
const seedWord = process.env.SEED || config.seed || "hello friend!";
|
|
58
65
|
config.seed = seedWord;
|
|
59
66
|
u.initChance(seedWord);
|
|
60
|
-
const chance = u.getChance();
|
|
61
|
-
config.chance = chance;
|
|
67
|
+
const chance = u.getChance(); // ! this is the only safe way to get the chance instance
|
|
62
68
|
let {
|
|
63
69
|
seed,
|
|
64
70
|
numEvents = 100000,
|
|
@@ -67,10 +73,9 @@ async function main(config) {
|
|
|
67
73
|
epochStart = 0,
|
|
68
74
|
epochEnd = dayjs().unix(),
|
|
69
75
|
events = [{ event: "foo" }, { event: "bar" }, { event: "baz" }],
|
|
70
|
-
superProps = {
|
|
76
|
+
superProps = { luckyNumber: [2, 2, 4, 4, 42, 42, 42, 2, 2, 4, 4, 42, 42, 42, 420] },
|
|
71
77
|
funnels = [],
|
|
72
78
|
userProps = {
|
|
73
|
-
favoriteColor: ["red", "green", "blue", "yellow"],
|
|
74
79
|
spiritAnimal: chance.animal.bind(chance),
|
|
75
80
|
},
|
|
76
81
|
scdProps = {},
|
|
@@ -88,10 +93,20 @@ async function main(config) {
|
|
|
88
93
|
makeChart = false,
|
|
89
94
|
soup = {},
|
|
90
95
|
hook = (record) => record,
|
|
96
|
+
hasAdSpend = false,
|
|
97
|
+
hasCampaigns = false,
|
|
98
|
+
hasLocation = false,
|
|
99
|
+
isAnonymous = false,
|
|
100
|
+
hasBrowser = false,
|
|
101
|
+
hasAndroidDevices = false,
|
|
102
|
+
hasDesktopDevices = false,
|
|
103
|
+
hasIOSDevices = false
|
|
91
104
|
} = config;
|
|
105
|
+
|
|
92
106
|
if (!config.superProps) config.superProps = superProps;
|
|
93
107
|
if (!config.userProps || Object.keys(config?.userProps)) config.userProps = userProps;
|
|
94
|
-
|
|
108
|
+
|
|
109
|
+
|
|
95
110
|
config.simulationName = makeName();
|
|
96
111
|
const { simulationName } = config;
|
|
97
112
|
if (epochStart && !numDays) numDays = dayjs.unix(epochEnd).diff(dayjs.unix(epochStart), "day");
|
|
@@ -123,13 +138,33 @@ async function main(config) {
|
|
|
123
138
|
config.makeChart = makeChart;
|
|
124
139
|
config.soup = soup;
|
|
125
140
|
config.hook = hook;
|
|
141
|
+
config.hasAdSpend = hasAdSpend;
|
|
142
|
+
config.hasCampaigns = hasCampaigns;
|
|
143
|
+
config.hasLocation = hasLocation;
|
|
144
|
+
config.isAnonymous = isAnonymous;
|
|
145
|
+
config.hasBrowser = hasBrowser;
|
|
146
|
+
config.hasAndroidDevices = hasAndroidDevices;
|
|
147
|
+
config.hasDesktopDevices = hasDesktopDevices;
|
|
148
|
+
config.hasIOSDevices = hasIOSDevices;
|
|
126
149
|
|
|
127
150
|
//event validation
|
|
128
151
|
const validatedEvents = u.validateEventConfig(events);
|
|
129
152
|
events = validatedEvents;
|
|
130
153
|
config.events = validatedEvents;
|
|
154
|
+
|
|
155
|
+
//globals
|
|
131
156
|
global.MP_SIMULATION_CONFIG = config;
|
|
132
157
|
CONFIG = config;
|
|
158
|
+
VERBOSE = verbose;
|
|
159
|
+
DEFAULTS = {
|
|
160
|
+
locations: u.pickAWinner(locations, 0),
|
|
161
|
+
iOSDevices: u.pickAWinner(devices.iosDevices, 0),
|
|
162
|
+
androidDevices: u.pickAWinner(devices.androidDevices, 0),
|
|
163
|
+
desktopDevices: u.pickAWinner(devices.desktopDevices, 0),
|
|
164
|
+
browsers: u.pickAWinner(devices.browsers, 0),
|
|
165
|
+
campaigns: u.pickAWinner(campaigns, 0),
|
|
166
|
+
};
|
|
167
|
+
|
|
133
168
|
const runId = uid(42);
|
|
134
169
|
let trackingParams = { runId, seed, numEvents, numUsers, numDays, anonIds, sessionIds, format, targetToken: token, region, writeToDisk, isCLI, version };
|
|
135
170
|
track('start simulation', trackingParams);
|
|
@@ -153,7 +188,7 @@ async function main(config) {
|
|
|
153
188
|
|
|
154
189
|
// if no funnels, make some out of events...
|
|
155
190
|
if (!funnels || !funnels.length) {
|
|
156
|
-
funnels = inferFunnels(events);
|
|
191
|
+
funnels = u.inferFunnels(events);
|
|
157
192
|
config.funnels = funnels;
|
|
158
193
|
CONFIG = config;
|
|
159
194
|
}
|
|
@@ -163,11 +198,19 @@ async function main(config) {
|
|
|
163
198
|
loopUsers: for (let i = 1; i < numUsers + 1; i++) {
|
|
164
199
|
u.progress("users", i);
|
|
165
200
|
const userId = chance.guid();
|
|
166
|
-
|
|
167
|
-
const user = u.generateUser(userId, numDays);
|
|
201
|
+
const user = u.person(userId, numDays, isAnonymous);
|
|
168
202
|
const { distinct_id, created, anonymousIds, sessionIds } = user;
|
|
169
203
|
let numEventsPreformed = 0;
|
|
170
204
|
|
|
205
|
+
if (hasLocation) {
|
|
206
|
+
const location = u.choose(DEFAULTS.locations().map(l => { delete l.country; return l; }));
|
|
207
|
+
for (const key in location) {
|
|
208
|
+
user[key] = location[key];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
171
214
|
// profile creation
|
|
172
215
|
const profile = makeProfile(userProps, user);
|
|
173
216
|
userProfilesData.hookPush(profile);
|
|
@@ -379,7 +422,7 @@ async function main(config) {
|
|
|
379
422
|
fixData: true,
|
|
380
423
|
verbose: false,
|
|
381
424
|
forceStream: true,
|
|
382
|
-
strict:
|
|
425
|
+
strict: false, //! sometimes we get events in the future... it happens
|
|
383
426
|
dryRun: false,
|
|
384
427
|
abridged: false,
|
|
385
428
|
fixJson: true,
|
|
@@ -452,27 +495,42 @@ async function main(config) {
|
|
|
452
495
|
* @param {Boolean} isFirstEvent=false
|
|
453
496
|
*/
|
|
454
497
|
function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEvent, superProps, groupKeys, isFirstEvent = false) {
|
|
455
|
-
const
|
|
498
|
+
const chance = u.getChance();
|
|
499
|
+
const { mean = 0, deviation = 2, peaks = 5 } = CONFIG.soup;
|
|
500
|
+
const { hasAndroidDevices, hasBrowser, hasCampaigns, hasDesktopDevices, hasIOSDevices, hasLocation } = CONFIG;
|
|
456
501
|
//event model
|
|
457
502
|
const eventTemplate = {
|
|
458
503
|
event: chosenEvent.event,
|
|
459
504
|
source: "dm4",
|
|
460
505
|
};
|
|
461
506
|
|
|
507
|
+
let defaultProps = {};
|
|
508
|
+
let devicePool = [];
|
|
509
|
+
if (hasLocation) defaultProps.location = DEFAULTS.locations().map(l => { delete l.country_code; return l; });
|
|
510
|
+
if (hasBrowser) defaultProps.browser = DEFAULTS.browsers();
|
|
511
|
+
if (hasAndroidDevices) devicePool.push(DEFAULTS.androidDevices());
|
|
512
|
+
if (hasIOSDevices) devicePool.push(DEFAULTS.iOSDevices());
|
|
513
|
+
if (hasDesktopDevices) devicePool.push(DEFAULTS.desktopDevices());
|
|
514
|
+
// we don't always have campaigns, because of attribution
|
|
515
|
+
if (hasCampaigns && chance.bool({ likelihood: 25 })) defaultProps.campaigns = DEFAULTS.campaigns();
|
|
516
|
+
const devices = devicePool.flat();
|
|
517
|
+
if (devices.length) defaultProps.device = devices;
|
|
518
|
+
|
|
519
|
+
|
|
462
520
|
//event time
|
|
463
521
|
if (earliestTime > NOW) {
|
|
464
522
|
earliestTime = dayjs.unix(NOW).subtract(2, 'd').unix();
|
|
465
523
|
};
|
|
466
524
|
|
|
467
525
|
if (isFirstEvent) eventTemplate.time = dayjs.unix(earliestTime).toISOString();
|
|
468
|
-
if (!isFirstEvent) eventTemplate.time = u.TimeSoup(earliestTime, NOW, peaks,
|
|
526
|
+
if (!isFirstEvent) eventTemplate.time = u.TimeSoup(earliestTime, NOW, peaks, deviation, mean);
|
|
469
527
|
|
|
470
528
|
// anonymous and session ids
|
|
471
|
-
if (CONFIG?.anonIds) eventTemplate.device_id =
|
|
472
|
-
if (CONFIG?.sessionIds) eventTemplate.session_id =
|
|
529
|
+
if (CONFIG?.anonIds) eventTemplate.device_id = chance.pickone(anonymousIds);
|
|
530
|
+
if (CONFIG?.sessionIds) eventTemplate.session_id = chance.pickone(sessionIds);
|
|
473
531
|
|
|
474
532
|
//sometimes have a user_id
|
|
475
|
-
if (!isFirstEvent &&
|
|
533
|
+
if (!isFirstEvent && chance.bool({ likelihood: 42 })) eventTemplate.user_id = distinct_id;
|
|
476
534
|
|
|
477
535
|
// ensure that there is a user_id or device_id
|
|
478
536
|
if (!eventTemplate.user_id && !eventTemplate.device_id) eventTemplate.user_id = distinct_id;
|
|
@@ -489,6 +547,34 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
|
|
|
489
547
|
}
|
|
490
548
|
}
|
|
491
549
|
|
|
550
|
+
//iterate through default properties
|
|
551
|
+
for (const key in defaultProps) {
|
|
552
|
+
if (Array.isArray(defaultProps[key])) {
|
|
553
|
+
const choice = u.choose(defaultProps[key]);
|
|
554
|
+
if (typeof choice === "string") {
|
|
555
|
+
if (!eventTemplate[key]) eventTemplate[key] = choice;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
else if (Array.isArray(choice)) {
|
|
559
|
+
for (const subChoice of choice) {
|
|
560
|
+
if (!eventTemplate[key]) eventTemplate[key] = subChoice;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
else if (typeof choice === "object") {
|
|
565
|
+
for (const subKey in choice) {
|
|
566
|
+
if (Array.isArray(choice[subKey])) {
|
|
567
|
+
const subChoice = u.choose(choice[subKey]);
|
|
568
|
+
if (!eventTemplate[subKey]) eventTemplate[subKey] = subChoice;
|
|
569
|
+
}
|
|
570
|
+
if (!eventTemplate[subKey]) eventTemplate[subKey] = choice[subKey];
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
492
578
|
//iterate through groups
|
|
493
579
|
for (const groupPair of groupKeys) {
|
|
494
580
|
const groupKey = groupPair[0];
|
|
@@ -518,6 +604,7 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
|
|
|
518
604
|
* @return {[EventSpec[], Boolean]}
|
|
519
605
|
*/
|
|
520
606
|
function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
607
|
+
const chance = u.getChance();
|
|
521
608
|
const { hook } = config;
|
|
522
609
|
hook(funnel, "funnel-pre", { user, profile, scd, funnel, config });
|
|
523
610
|
let {
|
|
@@ -532,6 +619,7 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
532
619
|
const { superProps, groupKeys } = config;
|
|
533
620
|
const { name, email } = profile;
|
|
534
621
|
|
|
622
|
+
//choose the properties for this funnel
|
|
535
623
|
const chosenFunnelProps = { ...props, ...superProps };
|
|
536
624
|
for (const key in props) {
|
|
537
625
|
try {
|
|
@@ -563,7 +651,7 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
563
651
|
.reduce((acc, step) => {
|
|
564
652
|
if (!requireRepeats) {
|
|
565
653
|
if (acc.find(e => e.event === step.event)) {
|
|
566
|
-
if (
|
|
654
|
+
if (chance.bool({ likelihood: 50 })) {
|
|
567
655
|
conversionRate = Math.floor(conversionRate * 1.25); //increase conversion rate
|
|
568
656
|
acc.push(step);
|
|
569
657
|
}
|
|
@@ -583,7 +671,7 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
583
671
|
return acc;
|
|
584
672
|
}, []);
|
|
585
673
|
|
|
586
|
-
let doesUserConvert =
|
|
674
|
+
let doesUserConvert = chance.bool({ likelihood: conversionRate });
|
|
587
675
|
let numStepsUserWillTake = sequence.length;
|
|
588
676
|
if (!doesUserConvert) numStepsUserWillTake = u.integer(1, sequence.length - 1);
|
|
589
677
|
const funnelTotalRelativeTimeInHours = timeToConvert / numStepsUserWillTake;
|
|
@@ -677,49 +765,6 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
677
765
|
}
|
|
678
766
|
|
|
679
767
|
|
|
680
|
-
function inferFunnels(events) {
|
|
681
|
-
const createdFunnels = [];
|
|
682
|
-
const firstEvents = events.filter((e) => e.isFirstEvent).map((e) => e.event);
|
|
683
|
-
const usageEvents = events.filter((e) => !e.isFirstEvent).map((e) => e.event);
|
|
684
|
-
const numFunnelsToCreate = Math.ceil(usageEvents.length);
|
|
685
|
-
/** @type {Funnel} */
|
|
686
|
-
const funnelTemplate = {
|
|
687
|
-
sequence: [],
|
|
688
|
-
conversionRate: 50,
|
|
689
|
-
order: 'sequential',
|
|
690
|
-
requireRepeats: false,
|
|
691
|
-
props: {},
|
|
692
|
-
timeToConvert: 1,
|
|
693
|
-
isFirstFunnel: false,
|
|
694
|
-
weight: 1
|
|
695
|
-
};
|
|
696
|
-
if (firstEvents.length) {
|
|
697
|
-
for (const event of firstEvents) {
|
|
698
|
-
createdFunnels.push({ ...clone(funnelTemplate), sequence: [event], isFirstFunnel: true, conversionRate: 100 });
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
//at least one funnel with all usage events
|
|
703
|
-
createdFunnels.push({ ...clone(funnelTemplate), sequence: usageEvents });
|
|
704
|
-
|
|
705
|
-
//for the rest, make random funnels
|
|
706
|
-
followUpFunnels: for (let i = 1; i < numFunnelsToCreate; i++) {
|
|
707
|
-
/** @type {Funnel} */
|
|
708
|
-
const funnel = { ...clone(funnelTemplate) };
|
|
709
|
-
funnel.conversionRate = u.integer(25, 75);
|
|
710
|
-
funnel.timeToConvert = u.integer(1, 10);
|
|
711
|
-
funnel.weight = u.integer(1, 10);
|
|
712
|
-
const sequence = u.shuffleArray(usageEvents).slice(0, u.integer(2, usageEvents.length));
|
|
713
|
-
funnel.sequence = sequence;
|
|
714
|
-
funnel.order = 'random';
|
|
715
|
-
createdFunnels.push(funnel);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
return createdFunnels;
|
|
719
|
-
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
|
|
723
768
|
function makeProfile(props, defaults) {
|
|
724
769
|
//build the spec
|
|
725
770
|
const profile = {
|
|
@@ -770,8 +815,69 @@ function makeSCD(prop, scdKey, distinct_id, mutations, created) {
|
|
|
770
815
|
return scdEntries;
|
|
771
816
|
}
|
|
772
817
|
|
|
773
|
-
|
|
774
|
-
|
|
818
|
+
//todo
|
|
819
|
+
// function makeAdSpend(spec, usersCreated, specialDays = []) {
|
|
820
|
+
// const networks = [
|
|
821
|
+
// ...new Set(
|
|
822
|
+
// attribution
|
|
823
|
+
// .filter(camp => !["$organic", "$referral"].includes(camp.utm_source.join()))
|
|
824
|
+
// .map(network => network.utm_source)
|
|
825
|
+
// .flat()
|
|
826
|
+
// )
|
|
827
|
+
// ];
|
|
828
|
+
|
|
829
|
+
// const campaigns = attribution.slice().pop().utm_campaign;
|
|
830
|
+
// const campaignParams = attribution.slice().pop();
|
|
831
|
+
// const { startDate, endDate } = spec;
|
|
832
|
+
// const days = getDeltaDays(startDate, endDate);
|
|
833
|
+
// const numDays = days.length;
|
|
834
|
+
// const data = [];
|
|
835
|
+
// for (const network of networks) {
|
|
836
|
+
// for (const day of days) {
|
|
837
|
+
// //cost per acquisition ~ $10-50
|
|
838
|
+
// let CAC = chance.integer({ min: 10, max: 50 });
|
|
839
|
+
// // daily spend is total users / total days * CAC / num networks
|
|
840
|
+
// let cost = Math.floor(((usersCreated / numDays) * CAC) / networks.length);
|
|
841
|
+
// // CTR ~ 0.5-10%
|
|
842
|
+
// let clicks = Math.floor(cost / chance.floating({ min: 0.5, max: 10 }));
|
|
843
|
+
// //boost CTR on "special days" ~ 15-50%
|
|
844
|
+
// if (day in specialDays) clicks *= Math.floor(clicks * chance.floating({ min: 1.15, max: 1.5 }));
|
|
845
|
+
// //impressions ~100-500 * cost
|
|
846
|
+
// let impressions = Math.floor(cost * chance.integer({ min: 100, max: 500 }));
|
|
847
|
+
// // views ~ 25-80% of impressions
|
|
848
|
+
// let views = Math.floor(impressions * chance.floating({ min: 0.25, max: 0.8 }));
|
|
849
|
+
|
|
850
|
+
// let campaign_name = chance.pickone(campaigns);
|
|
851
|
+
// let campaign_id = md5(campaign_name);
|
|
852
|
+
// data.push({
|
|
853
|
+
// event: "ad data",
|
|
854
|
+
// properties: {
|
|
855
|
+
// cost,
|
|
856
|
+
// clicks,
|
|
857
|
+
// impressions,
|
|
858
|
+
// campaign_name,
|
|
859
|
+
// campaign_id,
|
|
860
|
+
// network,
|
|
861
|
+
// views,
|
|
862
|
+
|
|
863
|
+
// //addendum
|
|
864
|
+
// utm_campaign: campaign_name,
|
|
865
|
+
// utm_source: network,
|
|
866
|
+
// utm_medium: campaignParams.utm_medium,
|
|
867
|
+
// utm_content: campaignParams.utm_content,
|
|
868
|
+
// utm_term: campaignParams.utm_term,
|
|
869
|
+
|
|
870
|
+
// source: network,
|
|
871
|
+
// $insert_id: campaign_id,
|
|
872
|
+
// time: dayjs.utc(day).hour(12).unix(),
|
|
873
|
+
// $source: "DM3",
|
|
874
|
+
// $mp_lib: "DM3"
|
|
875
|
+
// }
|
|
876
|
+
// });
|
|
877
|
+
// }
|
|
878
|
+
// }
|
|
879
|
+
// return data;
|
|
880
|
+
// }
|
|
775
881
|
|
|
776
882
|
|
|
777
883
|
|
|
@@ -782,7 +888,7 @@ function makeSCD(prop, scdKey, distinct_id, mutations, created) {
|
|
|
782
888
|
// this is for CLI
|
|
783
889
|
if (require.main === module) {
|
|
784
890
|
isCLI = true;
|
|
785
|
-
const args =
|
|
891
|
+
const args = getCliParams();
|
|
786
892
|
// @ts-ignore
|
|
787
893
|
let { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk, complex = false, sessionIds, anonIds } = args;
|
|
788
894
|
// @ts-ignore
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-mp-data",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.02",
|
|
4
4
|
"description": "builds all mixpanel primitives for a given project",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types.d.ts",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"chartjs-node-canvas": "^4.1.6",
|
|
48
48
|
"dayjs": "^1.11.11",
|
|
49
49
|
"dotenv": "^16.4.5",
|
|
50
|
-
"mixpanel-import": "^2.5.
|
|
50
|
+
"mixpanel-import": "^2.5.55",
|
|
51
51
|
"yargs": "^17.7.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
@@ -57,4 +57,4 @@
|
|
|
57
57
|
"jest": {
|
|
58
58
|
"preset": "./tests/jest.config.js"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|
package/schemas/simple.js
CHANGED
|
@@ -31,6 +31,16 @@ const config = {
|
|
|
31
31
|
region: "US",
|
|
32
32
|
anonIds: false, //if true, anonymousIds are created for each user
|
|
33
33
|
sessionIds: false, //if true, sessionIds are created for each user
|
|
34
|
+
hasAdSpend: false,
|
|
35
|
+
|
|
36
|
+
hasLocation: true,
|
|
37
|
+
hasAndroidDevices: true,
|
|
38
|
+
hasIOSDevices: true,
|
|
39
|
+
hasDesktopDevices: true,
|
|
40
|
+
hasBrowser: true,
|
|
41
|
+
hasCampaigns: true,
|
|
42
|
+
isAnonymous: false,
|
|
43
|
+
|
|
34
44
|
|
|
35
45
|
events: [
|
|
36
46
|
{
|
package/scratch.mjs
CHANGED
|
@@ -10,10 +10,11 @@ import execSync from 'child_process';
|
|
|
10
10
|
|
|
11
11
|
/** @type {main.Config} */
|
|
12
12
|
const spec = {
|
|
13
|
-
...
|
|
13
|
+
...simple,
|
|
14
14
|
writeToDisk: false,
|
|
15
15
|
verbose: true,
|
|
16
|
-
makeChart: false,
|
|
16
|
+
makeChart: false,
|
|
17
|
+
token: "e98e6af94f6ddfb5e967fa265484539a"
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
|
package/tests/unit.test.js
CHANGED
|
@@ -43,7 +43,8 @@ const { applySkew,
|
|
|
43
43
|
validateEventConfig,
|
|
44
44
|
validateTime,
|
|
45
45
|
interruptArray,
|
|
46
|
-
optimizedBoxMuller
|
|
46
|
+
optimizedBoxMuller,
|
|
47
|
+
inferFunnels
|
|
47
48
|
} = require('../utils');
|
|
48
49
|
|
|
49
50
|
|
|
@@ -374,7 +375,7 @@ describe('utilities', () => {
|
|
|
374
375
|
|
|
375
376
|
|
|
376
377
|
test('person: fields', () => {
|
|
377
|
-
const generatedPerson = person();
|
|
378
|
+
const generatedPerson = person('myId');
|
|
378
379
|
expect(generatedPerson).toHaveProperty('name');
|
|
379
380
|
expect(generatedPerson).toHaveProperty('email');
|
|
380
381
|
expect(generatedPerson).toHaveProperty('avatar');
|
|
@@ -628,8 +629,8 @@ describe('utilities', () => {
|
|
|
628
629
|
const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
629
630
|
const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
|
|
630
631
|
const stdDev = Math.sqrt(variance);
|
|
631
|
-
expect(mean).
|
|
632
|
-
expect(stdDev).
|
|
632
|
+
expect(mean).toBeLessThan(1);
|
|
633
|
+
expect(stdDev).toBeLessThan(1);
|
|
633
634
|
});
|
|
634
635
|
|
|
635
636
|
|
package/types.d.ts
CHANGED
|
@@ -12,10 +12,21 @@ declare namespace main {
|
|
|
12
12
|
epochStart?: number;
|
|
13
13
|
epochEnd?: number;
|
|
14
14
|
numEvents?: number;
|
|
15
|
-
numUsers?: number;
|
|
15
|
+
numUsers?: number;
|
|
16
|
+
|
|
17
|
+
//switches
|
|
18
|
+
isAnonymous?: boolean;
|
|
19
|
+
hasLocation?: boolean;
|
|
20
|
+
hasCampaigns?: boolean;
|
|
21
|
+
hasAdSpend?: boolean;
|
|
22
|
+
hasIOSDevices?: boolean;
|
|
23
|
+
hasAndroidDevices?: boolean;
|
|
24
|
+
hasDesktopDevices?: boolean;
|
|
25
|
+
hasBrowser?: boolean;
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
format?: "csv" | "json";
|
|
17
29
|
region?: "US" | "EU";
|
|
18
|
-
chance?: any;
|
|
19
30
|
events?: EventConfig[]; //can also be a array of strings
|
|
20
31
|
superProps?: Record<string, ValueValid>;
|
|
21
32
|
funnels?: Funnel[];
|
|
@@ -50,6 +61,8 @@ declare namespace main {
|
|
|
50
61
|
| "mirror"
|
|
51
62
|
| "funnel-pre"
|
|
52
63
|
| "funnel-post"
|
|
64
|
+
| "ad-spend"
|
|
65
|
+
| "churn"
|
|
53
66
|
| "";
|
|
54
67
|
export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
|
|
55
68
|
|