make-mp-data 1.4.2 → 1.4.3
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/README.md +3 -2
- package/{cli.js → core/cli.js} +1 -1
- package/{defaults.js → core/defaults.js} +48 -62
- package/{index.js → core/index.js} +113 -89
- package/{utils.js → core/utils.js} +139 -69
- package/package.json +7 -8
- package/schemas/anon.js +104 -0
- package/schemas/complex.js +11 -2
- package/schemas/deepNest.js +5 -1
- package/schemas/foobar.js +2 -2
- package/schemas/funnels.js +1 -1
- package/schemas/simple.js +1 -1
- package/scratch.mjs +18 -5
- package/scripts/jsdoctest.js +1 -1
- package/tests/e2e.test.js +25 -7
- package/{testSoup.mjs → tests/testSoup.mjs} +2 -2
- package/tests/unit.test.js +153 -6
- package/tsconfig.json +1 -1
- package/types.d.ts +2 -1
- /package/{chart.js → core/chart.js} +0 -0
- /package/{testCases.mjs → tests/testCases.mjs} +0 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ the CLI looks like this:
|
|
|
9
9
|
|
|
10
10
|

|
|
11
11
|
|
|
12
|
-
under the hood, `make-mp-data` is modeling data adherent to match [Mixpanel's data model](https://docs.mixpanel.com/docs/data-structure/concepts), giving you the tools you need for robust,
|
|
12
|
+
under the hood, `make-mp-data` is modeling data adherent to match [Mixpanel's data model](https://docs.mixpanel.com/docs/data-structure/concepts), giving you the tools you need for robust, realistic field ready test data.
|
|
13
13
|
|
|
14
14
|
## 🚀 Quick Start
|
|
15
15
|
|
|
@@ -64,8 +64,9 @@ npx make-mp-data [dataModel.js] [options]
|
|
|
64
64
|
Example:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
npx make-mp-data
|
|
67
|
+
npx make-mp-data myDataSpec.js --token 1234 --numDays 30 --numUsers 1000 --numEvents 1000000
|
|
68
68
|
```
|
|
69
|
+
where `myDataSpec.js` exports a [JS object of this shape](https://github.com/ak--47/make-mp-data/blob/main/types.d.ts#L8) ... (see [`./schemas`](https://github.com/ak--47/make-mp-data/tree/main/schemas) for examples)
|
|
69
70
|
|
|
70
71
|
### Data Models
|
|
71
72
|
|
package/{cli.js → core/cli.js}
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* cSpell:disable */
|
|
2
2
|
//? https://docs.mixpanel.com/docs/data-structure/property-reference#default-properties
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const domainSuffix = ["com", "com", "com", "com", "net", "org", "net", "org", "io", "co", "co.uk", "us", "biz", "info", "gov", "edu"];
|
|
5
|
+
const domainPrefix = ["gmail", "gmail", "gmail", "gmail", "gmail", "gmail", "yahoo", "yahoo", "icloud", "icloud", "icloud", "icloud", "hotmail", "hotmail", "gmail", "gmail", "gmail", "gmail", "gmail", "gmail", "yahoo", "yahoo", "icloud", "icloud", "icloud", "hotmail", "hotmail", "outlook", "aol", "outlook", "aol", "protonmail", "zoho", "gmx", "yandex", "mail", "inbox", "fastmail", "tutanota", "mailfence", "disroot", "riseup", "posteo", "runbox", "kolabnow", "mailbox", "scryptmail", "ctemplar", "countermail", "hushmail", "startmail", "privatemail"];
|
|
5
6
|
const campaigns = [
|
|
6
7
|
{
|
|
7
8
|
utm_campaign: ["$organic"],
|
|
@@ -12,77 +13,60 @@ const campaigns = [
|
|
|
12
13
|
},
|
|
13
14
|
{
|
|
14
15
|
utm_source: ["facebook"],
|
|
15
|
-
utm_campaign: ["
|
|
16
|
-
utm_medium: ["
|
|
17
|
-
utm_content: ["
|
|
18
|
-
utm_term: ["
|
|
16
|
+
utm_campaign: ["fb_free_trial", "fb_discount_US", "fb_summer_sale", "fb_black_friday", "fb_lookalike_audience"],
|
|
17
|
+
utm_medium: ["social_influencer", "paid_promoted", "ad_sidebar", "sponsored_search"],
|
|
18
|
+
utm_content: ["fb_control_group", "fb_variant_A", "fb_variant_B", "fb_variant_C", "fb_variant_D"],
|
|
19
|
+
utm_term: ["fb_jan_feb", "fb_mar_apr", "fb_may_jun", "fb_jul_aug", "fb_sep_oct", "fb_nov_dec"]
|
|
19
20
|
},
|
|
20
21
|
{
|
|
21
|
-
utm_source: ["
|
|
22
|
-
utm_campaign: ["
|
|
23
|
-
utm_medium: ["
|
|
24
|
-
utm_content: ["
|
|
25
|
-
utm_term: ["
|
|
22
|
+
utm_source: ["snapchat"],
|
|
23
|
+
utm_campaign: ["sc_free_trial", "sc_discount_US", "sc_spring_sale", "sc_cyber_monday", "sc_lookalike_audience"],
|
|
24
|
+
utm_medium: ["promoted_tweet", "sponsored_post", "sidebar_ad", "search_ad"],
|
|
25
|
+
utm_content: ["sc_control_group", "sc_variant_A", "sc_variant_B", "sc_variant_C", "sc_variant_D"],
|
|
26
|
+
utm_term: ["sc_jan_feb", "sc_mar_apr", "sc_may_jun", "sc_jul_aug", "sc_sep_oct", "sc_nov_dec"]
|
|
26
27
|
},
|
|
28
|
+
|
|
27
29
|
{
|
|
28
30
|
utm_source: ["linkedin"],
|
|
29
|
-
utm_campaign: ["
|
|
30
|
-
utm_medium: ["
|
|
31
|
-
utm_content: ["
|
|
32
|
-
utm_term: ["
|
|
31
|
+
utm_campaign: ["li_free_trial", "li_discount_US", "li_fall_sale", "li_holiday_special", "li_lookalike_audience"],
|
|
32
|
+
utm_medium: ["influencer_post", "promoted_content", "sidebar_ad", "search_ad"],
|
|
33
|
+
utm_content: ["li_control_group", "li_variant_A", "li_variant_B", "li_variant_C", "li_variant_D"],
|
|
34
|
+
utm_term: ["li_jan_feb", "li_mar_apr", "li_may_jun", "li_jul_aug", "li_sep_oct", "li_nov_dec"]
|
|
33
35
|
},
|
|
34
|
-
|
|
35
36
|
{
|
|
36
37
|
utm_source: ["instagram"],
|
|
37
|
-
utm_campaign: ["
|
|
38
|
-
utm_medium: ["
|
|
39
|
-
utm_content: ["
|
|
40
|
-
utm_term: ["
|
|
38
|
+
utm_campaign: ["ig_free_trial", "ig_discount_US", "ig_winter_sale", "ig_flash_sale", "ig_lookalike_audience"],
|
|
39
|
+
utm_medium: ["story_ad", "influencer_post", "promoted_post", "search_ad"],
|
|
40
|
+
utm_content: ["ig_control_group", "ig_variant_A", "ig_variant_B", "ig_variant_C", "ig_variant_D"],
|
|
41
|
+
utm_term: ["ig_jan_feb", "ig_mar_apr", "ig_may_jun", "ig_jul_aug", "ig_sep_oct", "ig_nov_dec"]
|
|
41
42
|
},
|
|
42
|
-
|
|
43
43
|
{
|
|
44
|
-
utm_source: ["google
|
|
45
|
-
utm_campaign: ["
|
|
46
|
-
utm_medium: ["
|
|
47
|
-
utm_content: ["
|
|
48
|
-
utm_term: ["
|
|
44
|
+
utm_source: ["google"],
|
|
45
|
+
utm_campaign: ["ga_free_trial", "ga_discount_US", "ga_spring_promo", "ga_summer_promo", "ga_lookalike_audience"],
|
|
46
|
+
utm_medium: ["search_ad", "display_ad", "sidebar_ad"],
|
|
47
|
+
utm_content: ["ga_control_group", "ga_variant_A", "ga_variant_B", "ga_variant_C", "ga_variant_D"],
|
|
48
|
+
utm_term: ["ga_jan_feb", "ga_mar_apr", "ga_may_jun", "ga_jul_aug", "ga_sep_oct", "ga_nov_dec"]
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
utm_source: ["youtube"],
|
|
52
|
-
utm_campaign: ["
|
|
53
|
-
utm_medium: ["
|
|
54
|
-
utm_content: ["
|
|
55
|
-
utm_term: ["
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
utm_source: ["$referral"],
|
|
59
|
-
utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
|
|
60
|
-
utm_medium: ["email", "referral link"],
|
|
61
|
-
utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
|
|
62
|
-
utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
utm_source: ["google ads"],
|
|
66
|
-
utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
|
|
67
|
-
utm_medium: ["inbox", "sidebar", "keywords"],
|
|
68
|
-
utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
|
|
69
|
-
utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
utm_campaign: ["yt_free_trial", "yt_discount_US", "yt_autumn_promo", "yt_end_of_year", "yt_lookalike_audience"],
|
|
53
|
+
utm_medium: ["video_ad", "display_ad", "sidebar_ad"],
|
|
54
|
+
utm_content: ["yt_control_group", "yt_variant_A", "yt_variant_B", "yt_variant_C", "yt_variant_D"],
|
|
55
|
+
utm_term: ["yt_jan_feb", "yt_mar_apr", "yt_may_jun", "yt_jul_aug", "yt_sep_oct", "yt_nov_dec"]
|
|
56
|
+
},
|
|
73
57
|
{
|
|
74
58
|
utm_source: ["tiktok"],
|
|
75
|
-
utm_campaign: ["
|
|
76
|
-
utm_medium: ["
|
|
77
|
-
utm_content: ["
|
|
78
|
-
utm_term: ["
|
|
59
|
+
utm_campaign: ["tt_free_trial", "tt_discount_US", "tt_flash_sale", "tt_special_offer", "tt_lookalike_audience"],
|
|
60
|
+
utm_medium: ["video_ad", "promoted_content", "sidebar_ad"],
|
|
61
|
+
utm_content: ["tt_control_group", "tt_variant_A", "tt_variant_B", "tt_variant_C", "tt_variant_D"],
|
|
62
|
+
utm_term: ["tt_jan_feb", "tt_mar_apr", "tt_may_jun", "tt_jul_aug", "tt_sep_oct", "tt_nov_dec"]
|
|
79
63
|
},
|
|
80
64
|
{
|
|
81
65
|
utm_source: ["reddit"],
|
|
82
|
-
utm_campaign: ["
|
|
83
|
-
utm_medium: ["
|
|
84
|
-
utm_content: ["
|
|
85
|
-
utm_term: ["
|
|
66
|
+
utm_campaign: ["rd_free_trial", "rd_discount_US", "rd_winter_promo", "rd_new_year_offer", "rd_lookalike_audience"],
|
|
67
|
+
utm_medium: ["promoted_post", "display_ad", "sidebar_ad"],
|
|
68
|
+
utm_content: ["rd_control_group", "rd_variant_A", "rd_variant_B", "rd_variant_C", "rd_variant_D"],
|
|
69
|
+
utm_term: ["rd_jan_feb", "rd_mar_apr", "rd_may_jun", "rd_jul_aug", "rd_sep_oct", "rd_nov_dec"]
|
|
86
70
|
}
|
|
87
71
|
];
|
|
88
72
|
|
|
@@ -338,13 +322,13 @@ const desktopDevices = [
|
|
|
338
322
|
screen_height: '2520',
|
|
339
323
|
screen_width: '4480',
|
|
340
324
|
os: 'macOS',
|
|
341
|
-
Platform
|
|
325
|
+
Platform: 'Desktop'
|
|
342
326
|
}, {
|
|
343
327
|
model: 'iMac 27-inch (2020)',
|
|
344
328
|
screen_height: '2880',
|
|
345
329
|
screen_width: '5120',
|
|
346
330
|
os: 'macOS',
|
|
347
|
-
Platform
|
|
331
|
+
Platform: 'Desktop'
|
|
348
332
|
}, {
|
|
349
333
|
model: 'Mac Mini (M2, 2023)',
|
|
350
334
|
screen_height: '2160',
|
|
@@ -356,31 +340,31 @@ const desktopDevices = [
|
|
|
356
340
|
screen_height: '1664',
|
|
357
341
|
screen_width: '2560',
|
|
358
342
|
os: 'macOS',
|
|
359
|
-
Platform
|
|
343
|
+
Platform: 'Desktop'
|
|
360
344
|
}, {
|
|
361
345
|
model: 'MacBook Pro 14-inch (M2, 2023)',
|
|
362
346
|
screen_height: '1964',
|
|
363
347
|
screen_width: '3024',
|
|
364
348
|
os: 'macOS',
|
|
365
|
-
Platform
|
|
349
|
+
Platform: 'Desktop'
|
|
366
350
|
}, {
|
|
367
351
|
model: 'MacBook Pro 16-inch (M2, 2023)',
|
|
368
352
|
screen_height: '2234',
|
|
369
353
|
screen_width: '3456',
|
|
370
354
|
os: 'macOS',
|
|
371
|
-
Platform
|
|
355
|
+
Platform: 'Desktop'
|
|
372
356
|
}, {
|
|
373
357
|
model: 'Mac Pro (2019)',
|
|
374
358
|
screen_height: '2160',
|
|
375
359
|
screen_width: '3840',
|
|
376
360
|
os: 'macOS',
|
|
377
|
-
Platform
|
|
361
|
+
Platform: 'Desktop'
|
|
378
362
|
}, {
|
|
379
363
|
model: 'iMac Pro (2017)',
|
|
380
364
|
screen_height: '2880',
|
|
381
365
|
screen_width: '5120',
|
|
382
366
|
os: 'macOS',
|
|
383
|
-
Platform
|
|
367
|
+
Platform: 'Desktop'
|
|
384
368
|
}, {
|
|
385
369
|
model: 'Dell XPS 15',
|
|
386
370
|
screen_height: '2160',
|
|
@@ -970,5 +954,7 @@ module.exports = {
|
|
|
970
954
|
iosDevices,
|
|
971
955
|
desktopDevices
|
|
972
956
|
},
|
|
973
|
-
locations
|
|
957
|
+
locations,
|
|
958
|
+
domainSuffix,
|
|
959
|
+
domainPrefix
|
|
974
960
|
};
|
|
@@ -6,18 +6,18 @@ by AK
|
|
|
6
6
|
ak@mixpanel.com
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
//todo: ads-data
|
|
10
|
-
//todo: cart analysis
|
|
11
9
|
//todo: churn ... is churnFunnel, possible to return, etc
|
|
12
|
-
//todo: fixedTimeFunnel? if set this funnel will occur for all users at the same time ['
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/** @typedef {import('
|
|
17
|
-
/** @typedef {import('
|
|
18
|
-
/** @typedef {import('
|
|
19
|
-
/** @typedef {import('
|
|
20
|
-
/** @typedef {import('
|
|
10
|
+
//todo: fixedTimeFunnel? if set this funnel will occur for all users at the same time ['cards charged', 'charge complete']
|
|
11
|
+
//todo: send SCD data to mixpanel
|
|
12
|
+
//todo: send and map lookup tables to mixpanel
|
|
13
|
+
|
|
14
|
+
/** @typedef {import('../types').Config} Config */
|
|
15
|
+
/** @typedef {import('../types').EventConfig} EventConfig */
|
|
16
|
+
/** @typedef {import('../types').Funnel} Funnel */
|
|
17
|
+
/** @typedef {import('../types').Person} Person */
|
|
18
|
+
/** @typedef {import('../types').SCDTableRow} SCDTableRow */
|
|
19
|
+
/** @typedef {import('../types').UserProfile} UserProfile */
|
|
20
|
+
/** @typedef {import('../types').EventSpec} EventSpec */
|
|
21
21
|
|
|
22
22
|
const dayjs = require("dayjs");
|
|
23
23
|
const utc = require("dayjs/plugin/utc");
|
|
@@ -29,10 +29,11 @@ const os = require("os");
|
|
|
29
29
|
const path = require("path");
|
|
30
30
|
const { comma, bytesHuman, makeName, md5, clone, tracker, uid } = require("ak-tools");
|
|
31
31
|
const { generateLineChart } = require('./chart.js');
|
|
32
|
-
const { version } = require('
|
|
32
|
+
const { version } = require('../package.json');
|
|
33
33
|
const mp = require("mixpanel-import");
|
|
34
34
|
const metrics = tracker("make-mp-data", "db99eb8f67ae50949a13c27cacf57d41", os.userInfo().username);
|
|
35
35
|
|
|
36
|
+
|
|
36
37
|
const u = require("./utils.js");
|
|
37
38
|
const getCliParams = require("./cli.js");
|
|
38
39
|
const { campaigns, devices, locations } = require('./defaults.js');
|
|
@@ -41,7 +42,7 @@ let VERBOSE = false;
|
|
|
41
42
|
let isCLI = false;
|
|
42
43
|
/** @type {Config} */
|
|
43
44
|
let CONFIG;
|
|
44
|
-
|
|
45
|
+
let CAMPAIGNS;
|
|
45
46
|
let DEFAULTS;
|
|
46
47
|
require('dotenv').config();
|
|
47
48
|
|
|
@@ -156,6 +157,7 @@ async function main(config) {
|
|
|
156
157
|
global.MP_SIMULATION_CONFIG = config;
|
|
157
158
|
CONFIG = config;
|
|
158
159
|
VERBOSE = verbose;
|
|
160
|
+
CAMPAIGNS = campaigns;
|
|
159
161
|
DEFAULTS = {
|
|
160
162
|
locations: u.pickAWinner(locations, 0),
|
|
161
163
|
iOSDevices: u.pickAWinner(devices.iosDevices, 0),
|
|
@@ -177,6 +179,7 @@ async function main(config) {
|
|
|
177
179
|
//setup all the data structures we will push into
|
|
178
180
|
const eventData = u.enrichArray([], { hook, type: "event", config });
|
|
179
181
|
const userProfilesData = u.enrichArray([], { hook, type: "user", config });
|
|
182
|
+
const adSpendData = u.enrichArray([], { hook, type: "ad-spend", config });
|
|
180
183
|
const scdTableKeys = Object.keys(scdProps);
|
|
181
184
|
const scdTableData = [];
|
|
182
185
|
for (const [index, key] of scdTableKeys.entries()) {
|
|
@@ -196,14 +199,14 @@ async function main(config) {
|
|
|
196
199
|
//user loop
|
|
197
200
|
log(`---------------SIMULATION----------------`, "\n\n");
|
|
198
201
|
loopUsers: for (let i = 1; i < numUsers + 1; i++) {
|
|
199
|
-
u.progress("users", i);
|
|
202
|
+
u.progress([["users", i], ["events", eventData.length]]);
|
|
200
203
|
const userId = chance.guid();
|
|
201
204
|
const user = u.person(userId, numDays, isAnonymous);
|
|
202
205
|
const { distinct_id, created, anonymousIds, sessionIds } = user;
|
|
203
206
|
let numEventsPreformed = 0;
|
|
204
207
|
|
|
205
208
|
if (hasLocation) {
|
|
206
|
-
const location = u.choose(DEFAULTS.locations().map(l => { delete l.country; return l; }));
|
|
209
|
+
const location = u.choose(clone(DEFAULTS.locations()).map(l => { delete l.country; return l; }));
|
|
207
210
|
for (const key in location) {
|
|
208
211
|
user[key] = location[key];
|
|
209
212
|
}
|
|
@@ -269,6 +272,17 @@ async function main(config) {
|
|
|
269
272
|
// end individual user loop
|
|
270
273
|
}
|
|
271
274
|
|
|
275
|
+
if (hasAdSpend) {
|
|
276
|
+
const days = u.datesBetween(epochStart, epochEnd);
|
|
277
|
+
for (const day of days) {
|
|
278
|
+
const dailySpendData = makeAdSpend(day);
|
|
279
|
+
for (const spendEvent of dailySpendData) {
|
|
280
|
+
adSpendData.hookPush(spendEvent);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
}
|
|
285
|
+
|
|
272
286
|
//flatten SCD tables
|
|
273
287
|
scdTableData.forEach((table, index) => scdTableData[index] = table.flat());
|
|
274
288
|
|
|
@@ -280,7 +294,7 @@ async function main(config) {
|
|
|
280
294
|
const groupCardinality = groupPair[1];
|
|
281
295
|
const groupProfiles = [];
|
|
282
296
|
for (let i = 1; i < groupCardinality + 1; i++) {
|
|
283
|
-
u.progress("groups", i);
|
|
297
|
+
u.progress([["groups", i]]);
|
|
284
298
|
const group = {
|
|
285
299
|
[groupKey]: i,
|
|
286
300
|
...makeProfile(groupProps[groupKey])
|
|
@@ -297,7 +311,7 @@ async function main(config) {
|
|
|
297
311
|
const { key, entries, attributes } = lookupTable;
|
|
298
312
|
const data = [];
|
|
299
313
|
for (let i = 1; i < entries + 1; i++) {
|
|
300
|
-
u.progress("lookups", i);
|
|
314
|
+
u.progress([["lookups", i]]);
|
|
301
315
|
const item = {
|
|
302
316
|
[key]: i,
|
|
303
317
|
...makeProfile(attributes),
|
|
@@ -352,11 +366,12 @@ async function main(config) {
|
|
|
352
366
|
}
|
|
353
367
|
}
|
|
354
368
|
|
|
355
|
-
const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, mirrorFiles, folder } =
|
|
369
|
+
const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, mirrorFiles, folder, adSpendFiles } =
|
|
356
370
|
u.buildFileNames(config);
|
|
357
371
|
const pairs = [
|
|
358
372
|
[eventFiles, [eventData]],
|
|
359
373
|
[userFiles, [userProfilesData]],
|
|
374
|
+
[adSpendFiles, [adSpendData]],
|
|
360
375
|
[scdFiles, scdTableData],
|
|
361
376
|
[groupFiles, groupProfilesData],
|
|
362
377
|
[lookupFiles, lookupTableData],
|
|
@@ -431,7 +446,7 @@ async function main(config) {
|
|
|
431
446
|
|
|
432
447
|
if (eventData) {
|
|
433
448
|
log(`importing events to mixpanel...\n`);
|
|
434
|
-
const imported = await mp(creds, eventData, {
|
|
449
|
+
const imported = await mp(creds, clone(eventData), {
|
|
435
450
|
recordType: "event",
|
|
436
451
|
...commonOpts,
|
|
437
452
|
});
|
|
@@ -440,19 +455,28 @@ async function main(config) {
|
|
|
440
455
|
}
|
|
441
456
|
if (userProfilesData) {
|
|
442
457
|
log(`importing user profiles to mixpanel...\n`);
|
|
443
|
-
const imported = await mp(creds, userProfilesData, {
|
|
458
|
+
const imported = await mp(creds, clone(userProfilesData), {
|
|
444
459
|
recordType: "user",
|
|
445
460
|
...commonOpts,
|
|
446
461
|
});
|
|
447
462
|
log(`\tsent ${comma(imported.success)} user profiles\n`);
|
|
448
463
|
importResults.users = imported;
|
|
449
464
|
}
|
|
465
|
+
if (adSpendData) {
|
|
466
|
+
log(`importing ad spend data to mixpanel...\n`);
|
|
467
|
+
const imported = await mp(creds, clone(adSpendData), {
|
|
468
|
+
recordType: "event",
|
|
469
|
+
...commonOpts,
|
|
470
|
+
});
|
|
471
|
+
log(`\tsent ${comma(imported.success)} ad spend events\n`);
|
|
472
|
+
importResults.adSpend = imported;
|
|
473
|
+
}
|
|
450
474
|
if (groupProfilesData) {
|
|
451
475
|
for (const groupProfiles of groupProfilesData) {
|
|
452
476
|
const groupKey = groupProfiles.key;
|
|
453
477
|
const data = groupProfiles.data;
|
|
454
478
|
log(`importing ${groupKey} profiles to mixpanel...\n`);
|
|
455
|
-
const imported = await mp({ token, groupKey }, data, {
|
|
479
|
+
const imported = await mp({ token, groupKey }, clone(data), {
|
|
456
480
|
recordType: "group",
|
|
457
481
|
...commonOpts,
|
|
458
482
|
|
|
@@ -475,6 +499,7 @@ async function main(config) {
|
|
|
475
499
|
groupProfilesData,
|
|
476
500
|
lookupTableData,
|
|
477
501
|
mirrorEventData,
|
|
502
|
+
adSpendData
|
|
478
503
|
};
|
|
479
504
|
}
|
|
480
505
|
|
|
@@ -506,7 +531,7 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
|
|
|
506
531
|
|
|
507
532
|
let defaultProps = {};
|
|
508
533
|
let devicePool = [];
|
|
509
|
-
if (hasLocation) defaultProps.location = DEFAULTS.locations().map(l => { delete l.country_code; return l; });
|
|
534
|
+
if (hasLocation) defaultProps.location = clone(DEFAULTS.locations()).map(l => { delete l.country_code; return l; });
|
|
510
535
|
if (hasBrowser) defaultProps.browser = DEFAULTS.browsers();
|
|
511
536
|
if (hasAndroidDevices) devicePool.push(DEFAULTS.androidDevices());
|
|
512
537
|
if (hasIOSDevices) devicePool.push(DEFAULTS.iOSDevices());
|
|
@@ -563,11 +588,20 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
|
|
|
563
588
|
|
|
564
589
|
else if (typeof choice === "object") {
|
|
565
590
|
for (const subKey in choice) {
|
|
566
|
-
if (
|
|
591
|
+
if (typeof choice[subKey] === "string") {
|
|
592
|
+
if (!eventTemplate[subKey]) eventTemplate[subKey] = choice[subKey];
|
|
593
|
+
}
|
|
594
|
+
else if (Array.isArray(choice[subKey])) {
|
|
567
595
|
const subChoice = u.choose(choice[subKey]);
|
|
568
596
|
if (!eventTemplate[subKey]) eventTemplate[subKey] = subChoice;
|
|
569
597
|
}
|
|
570
|
-
|
|
598
|
+
|
|
599
|
+
else if (typeof choice[subKey] === "object") {
|
|
600
|
+
for (const subSubKey in choice[subKey]) {
|
|
601
|
+
if (!eventTemplate[subSubKey]) eventTemplate[subSubKey] = choice[subKey][subSubKey];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
571
605
|
}
|
|
572
606
|
}
|
|
573
607
|
|
|
@@ -787,7 +821,7 @@ function makeProfile(props, defaults) {
|
|
|
787
821
|
}
|
|
788
822
|
|
|
789
823
|
/**
|
|
790
|
-
* @param {import('
|
|
824
|
+
* @param {import('../types').ValueValid} prop
|
|
791
825
|
* @param {string} scdKey
|
|
792
826
|
* @param {string} distinct_id
|
|
793
827
|
* @param {number} mutations
|
|
@@ -816,68 +850,58 @@ function makeSCD(prop, scdKey, distinct_id, mutations, created) {
|
|
|
816
850
|
}
|
|
817
851
|
|
|
818
852
|
//todo
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
//
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
//
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
//
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
//
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
//
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
-
// }
|
|
853
|
+
function makeAdSpend(day) {
|
|
854
|
+
const chance = u.getChance();
|
|
855
|
+
const adSpendEvents = [];
|
|
856
|
+
for (const network of CAMPAIGNS) {
|
|
857
|
+
const campaigns = network.utm_campaign;
|
|
858
|
+
loopCampaigns: for (const campaign of campaigns) {
|
|
859
|
+
if (campaign === "$organic") continue loopCampaigns;
|
|
860
|
+
|
|
861
|
+
const CAC = u.integer(42, 420); //todo: get the # of users created in this day from eventData
|
|
862
|
+
// Randomly generating cost
|
|
863
|
+
const cost = chance.floating({ min: 10, max: 250, fixed: 2 });
|
|
864
|
+
|
|
865
|
+
// Ensuring realistic CPC and CTR
|
|
866
|
+
const avgCPC = chance.floating({ min: 0.33, max: 2.00, fixed: 4 });
|
|
867
|
+
const avgCTR = chance.floating({ min: 0.05, max: 0.25, fixed: 4 });
|
|
868
|
+
|
|
869
|
+
// Deriving impressions from cost and avg CPC
|
|
870
|
+
const clicks = Math.floor(cost / avgCPC);
|
|
871
|
+
const impressions = Math.floor(clicks / avgCTR);
|
|
872
|
+
const views = Math.floor(impressions * avgCTR);
|
|
873
|
+
|
|
874
|
+
//tags
|
|
875
|
+
const utm_medium = u.choose(u.pickAWinner(network.utm_medium)());
|
|
876
|
+
const utm_content = u.choose(u.pickAWinner(network.utm_content)());
|
|
877
|
+
const utm_term = u.choose(u.pickAWinner(network.utm_term)());
|
|
878
|
+
//each of these is a campaign
|
|
879
|
+
const adSpendEvent = {
|
|
880
|
+
event: "Ad Data",
|
|
881
|
+
time: day,
|
|
882
|
+
source: 'dm4',
|
|
883
|
+
utm_campaign: campaign,
|
|
884
|
+
campaign_id: md5(network.utm_source[0] + '-' + campaign),
|
|
885
|
+
network: network.utm_source[0].toUpperCase(),
|
|
886
|
+
distinct_id: network.utm_source[0].toUpperCase(),
|
|
887
|
+
utm_source: network.utm_source[0],
|
|
888
|
+
utm_medium,
|
|
889
|
+
utm_content,
|
|
890
|
+
utm_term,
|
|
891
|
+
|
|
892
|
+
clicks,
|
|
893
|
+
views,
|
|
894
|
+
impressions,
|
|
895
|
+
cost,
|
|
896
|
+
date: dayjs(day).format("YYYY-MM-DD"),
|
|
897
|
+
};
|
|
898
|
+
adSpendEvents.push(adSpendEvent);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
}
|
|
903
|
+
return adSpendEvents;
|
|
904
|
+
}
|
|
881
905
|
|
|
882
906
|
|
|
883
907
|
|
|
@@ -905,12 +929,12 @@ if (require.main === module) {
|
|
|
905
929
|
console.log(`... using default COMPLEX configuration [everything] ...\n`);
|
|
906
930
|
console.log(`... for more simple data, don't use the --complex flag ...\n`);
|
|
907
931
|
console.log(`... or specify your own js config file (see docs or --help) ...\n`);
|
|
908
|
-
config = require(path.resolve(__dirname, "
|
|
932
|
+
config = require(path.resolve(__dirname, "../schemas/complex.js"));
|
|
909
933
|
}
|
|
910
934
|
else {
|
|
911
935
|
console.log(`... using default SIMPLE configuration [events + users] ...\n`);
|
|
912
936
|
console.log(`... for more complex data, use the --complex flag ...\n`);
|
|
913
|
-
config = require(path.resolve(__dirname, "
|
|
937
|
+
config = require(path.resolve(__dirname, "../schemas/simple.js"));
|
|
914
938
|
}
|
|
915
939
|
}
|
|
916
940
|
|