make-mp-data 1.3.4 → 1.4.0
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/launch.json +11 -3
- package/.vscode/settings.json +11 -1
- package/README.md +2 -2
- package/chart.js +180 -0
- package/index.js +443 -301
- package/package.json +59 -52
- package/{models → schemas}/complex.js +18 -18
- package/{models → schemas}/foobar.js +1 -1
- package/schemas/funnels.js +222 -0
- package/{models → schemas}/simple.js +10 -10
- package/scratch.mjs +20 -0
- package/testCases.mjs +229 -0
- package/testSoup.mjs +27 -0
- package/tests/e2e.test.js +27 -20
- package/tests/jest.config.js +30 -0
- package/tests/unit.test.js +346 -14
- package/tmp/.gitkeep +0 -0
- package/tsconfig.json +1 -1
- package/types.d.ts +74 -15
- package/utils.js +590 -151
- package/timesoup.js +0 -92
- /package/{models → schemas}/deepNest.js +0 -0
package/index.js
CHANGED
|
@@ -6,25 +6,29 @@ by AK
|
|
|
6
6
|
ak@mixpanel.com
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const RUNTIME = process.env.RUNTIME || "unspecified";
|
|
10
|
-
const mp = require("mixpanel-import");
|
|
11
|
-
const path = require("path");
|
|
12
|
-
const Chance = require("chance");
|
|
13
|
-
const chance = new Chance();
|
|
14
|
-
const { comma, bytesHuman, mkdir, makeName, md5, clone, tracker, uid } = require("ak-tools");
|
|
15
|
-
const u = require("./utils.js");
|
|
16
|
-
const AKsTimeSoup = require("./timesoup.js");
|
|
17
9
|
const dayjs = require("dayjs");
|
|
18
10
|
const utc = require("dayjs/plugin/utc");
|
|
19
11
|
dayjs.extend(utc);
|
|
12
|
+
const NOW = dayjs('2024-02-02').unix(); //this is a FIXED POINT and we will shift it later
|
|
13
|
+
global.NOW = NOW;
|
|
14
|
+
const mp = require("mixpanel-import");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const { comma, bytesHuman, makeName, md5, clone, tracker, uid } = require("ak-tools");
|
|
17
|
+
const { generateLineChart } = require('./chart.js');
|
|
18
|
+
const { version } = require('./package.json');
|
|
19
|
+
const os = require("os");
|
|
20
|
+
const metrics = tracker("make-mp-data", "db99eb8f67ae50949a13c27cacf57d41", os.userInfo().username);
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const u = require("./utils.js");
|
|
20
24
|
const cliParams = require("./cli.js");
|
|
21
|
-
|
|
25
|
+
|
|
22
26
|
let VERBOSE = false;
|
|
23
27
|
let isCLI = false;
|
|
28
|
+
let CONFIG;
|
|
29
|
+
require('dotenv').config();
|
|
30
|
+
|
|
24
31
|
|
|
25
|
-
const { version } = require('./package.json');
|
|
26
|
-
const os = require("os");
|
|
27
|
-
const metrics = tracker("make-mp-data", "db99eb8f67ae50949a13c27cacf57d41", os.userInfo().username);
|
|
28
32
|
function track(name, props, ...rest) {
|
|
29
33
|
if (process.env.NODE_ENV === 'test') return;
|
|
30
34
|
metrics(name, props, ...rest);
|
|
@@ -32,19 +36,33 @@ function track(name, props, ...rest) {
|
|
|
32
36
|
|
|
33
37
|
/** @typedef {import('./types.d.ts').Config} Config */
|
|
34
38
|
/** @typedef {import('./types.d.ts').EventConfig} EventConfig */
|
|
39
|
+
/** @typedef {import('./types.d.ts').Funnel} Funnel */
|
|
40
|
+
/** @typedef {import('./types.d.ts').Person} Person */
|
|
41
|
+
/** @typedef {import('./types.d.ts').SCDTableRow} SCDTableRow */
|
|
42
|
+
/** @typedef {import('./types.d.ts').UserProfile} UserProfile */
|
|
43
|
+
/** @typedef {import('./types.d.ts').EventSpec} EventSpec */
|
|
35
44
|
|
|
36
45
|
/**
|
|
37
46
|
* generates fake mixpanel data
|
|
38
47
|
* @param {Config} config
|
|
39
48
|
*/
|
|
40
49
|
async function main(config) {
|
|
50
|
+
//PARAMS
|
|
51
|
+
const seedWord = process.env.SEED || config.seed || "hello friend!";
|
|
52
|
+
config.seed = seedWord;
|
|
53
|
+
u.initChance(seedWord);
|
|
54
|
+
const chance = u.getChance();
|
|
55
|
+
config.chance = chance;
|
|
41
56
|
let {
|
|
42
|
-
seed
|
|
57
|
+
seed,
|
|
43
58
|
numEvents = 100000,
|
|
44
59
|
numUsers = 1000,
|
|
45
60
|
numDays = 30,
|
|
61
|
+
epochStart = 0,
|
|
62
|
+
epochEnd = dayjs().unix(),
|
|
46
63
|
events = [{ event: "foo" }, { event: "bar" }, { event: "baz" }],
|
|
47
64
|
superProps = { platform: ["web", "iOS", "Android"] },
|
|
65
|
+
funnels = [],
|
|
48
66
|
userProps = {
|
|
49
67
|
favoriteColor: ["red", "green", "blue", "yellow"],
|
|
50
68
|
spiritAnimal: chance.animal.bind(chance),
|
|
@@ -61,131 +79,185 @@ async function main(config) {
|
|
|
61
79
|
region = "US",
|
|
62
80
|
writeToDisk = false,
|
|
63
81
|
verbose = false,
|
|
82
|
+
makeChart = false,
|
|
83
|
+
soup = {},
|
|
64
84
|
hook = (record) => record,
|
|
65
85
|
} = config;
|
|
86
|
+
if (!config.superProps) config.superProps = superProps;
|
|
87
|
+
if (!config.userProps || Object.keys(config?.userProps)) config.userProps = userProps;
|
|
66
88
|
VERBOSE = verbose;
|
|
67
89
|
config.simulationName = makeName();
|
|
68
90
|
const { simulationName } = config;
|
|
91
|
+
if (epochStart && !numDays) numDays = dayjs.unix(epochEnd).diff(dayjs.unix(epochStart), "day");
|
|
92
|
+
if (!epochStart && numDays) epochStart = dayjs.unix(epochEnd).subtract(numDays, "day").unix();
|
|
93
|
+
if (epochStart && numDays) { } //noop
|
|
94
|
+
if (!epochStart && !numDays) debugger; //never happens
|
|
95
|
+
config.seed = seed;
|
|
96
|
+
config.numEvents = numEvents;
|
|
97
|
+
config.numUsers = numUsers;
|
|
98
|
+
config.numDays = numDays;
|
|
99
|
+
config.epochStart = epochStart;
|
|
100
|
+
config.epochEnd = epochEnd;
|
|
101
|
+
config.events = events;
|
|
102
|
+
config.superProps = superProps;
|
|
103
|
+
config.funnels = funnels;
|
|
104
|
+
config.userProps = userProps;
|
|
105
|
+
config.scdProps = scdProps;
|
|
106
|
+
config.mirrorProps = mirrorProps;
|
|
107
|
+
config.groupKeys = groupKeys;
|
|
108
|
+
config.groupProps = groupProps;
|
|
109
|
+
config.lookupTables = lookupTables;
|
|
110
|
+
config.anonIds = anonIds;
|
|
111
|
+
config.sessionIds = sessionIds;
|
|
112
|
+
config.format = format;
|
|
113
|
+
config.token = token;
|
|
114
|
+
config.region = region;
|
|
115
|
+
config.writeToDisk = writeToDisk;
|
|
116
|
+
config.verbose = verbose;
|
|
117
|
+
config.makeChart = makeChart;
|
|
118
|
+
config.soup = soup;
|
|
119
|
+
config.hook = hook;
|
|
120
|
+
|
|
121
|
+
//event validation
|
|
122
|
+
const validatedEvents = validateEvents(events);
|
|
123
|
+
events = validatedEvents;
|
|
124
|
+
config.events = validatedEvents;
|
|
69
125
|
global.MP_SIMULATION_CONFIG = config;
|
|
70
|
-
|
|
126
|
+
CONFIG = config;
|
|
71
127
|
const runId = uid(42);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
numEvents,
|
|
76
|
-
numUsers,
|
|
77
|
-
numDays,
|
|
78
|
-
anonIds,
|
|
79
|
-
sessionIds,
|
|
80
|
-
format,
|
|
81
|
-
targetToken: token,
|
|
82
|
-
region,
|
|
83
|
-
writeToDisk,
|
|
84
|
-
isCLI,
|
|
85
|
-
version
|
|
86
|
-
});
|
|
128
|
+
let trackingParams = { runId, seed, numEvents, numUsers, numDays, anonIds, sessionIds, format, targetToken: token, region, writeToDisk, isCLI, version };
|
|
129
|
+
track('start simulation', trackingParams);
|
|
130
|
+
|
|
87
131
|
log(`------------------SETUP------------------`);
|
|
88
132
|
log(`\nyour data simulation will heretofore be known as: \n\n\t${simulationName.toUpperCase()}...\n`);
|
|
89
133
|
log(`and your configuration is:\n\n`, JSON.stringify({ seed, numEvents, numUsers, numDays, format, token, region, writeToDisk, anonIds, sessionIds }, null, 2));
|
|
90
134
|
log(`------------------SETUP------------------`, "\n");
|
|
91
135
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Scale and shift the normally distributed value to fit the range of days
|
|
101
|
-
const maxZ = u.integer(2, 4);
|
|
102
|
-
const scaledZ = (z / maxZ + 1) / 2;
|
|
103
|
-
const daysAgoBorn = Math.round(scaledZ * (numDays - 1)) + 1;
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
distinct_id,
|
|
107
|
-
...u.person(daysAgoBorn),
|
|
108
|
-
};
|
|
136
|
+
//setup all the data structures we will push into
|
|
137
|
+
const eventData = u.enrichArray([], { hook, type: "event", config });
|
|
138
|
+
const userProfilesData = u.enrichArray([], { hook, type: "user", config });
|
|
139
|
+
const scdTableKeys = Object.keys(scdProps);
|
|
140
|
+
const scdTableData = [];
|
|
141
|
+
for (const [index, key] of scdTableKeys.entries()) {
|
|
142
|
+
scdTableData[index] = u.enrichArray([], { hook, type: "scd", config, scdKey: key });
|
|
109
143
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
144
|
+
const groupProfilesData = u.enrichArray([], { hook, type: "group", config });
|
|
145
|
+
const lookupTableData = u.enrichArray([], { hook, type: "lookup", config });
|
|
146
|
+
const avgEvPerUser = Math.ceil(numEvents / numUsers);
|
|
147
|
+
|
|
148
|
+
// if no funnels, make some out of events...
|
|
149
|
+
if (!funnels || !funnels.length) {
|
|
150
|
+
const createdFunnels = [];
|
|
151
|
+
const firstEvents = events.filter((e) => e.isFirstEvent).map((e) => e.event);
|
|
152
|
+
const usageEvents = events.filter((e) => !e.isFirstEvent).map((e) => e.event);
|
|
153
|
+
const numFunnelsToCreate = Math.ceil(usageEvents.length);
|
|
154
|
+
/** @type {Funnel} */
|
|
155
|
+
const funnelTemplate = {
|
|
156
|
+
sequence: [],
|
|
157
|
+
conversionRate: 50,
|
|
158
|
+
order: 'sequential',
|
|
159
|
+
props: {},
|
|
160
|
+
timeToConvert: 1,
|
|
161
|
+
isFirstFunnel: false,
|
|
162
|
+
weight: 1
|
|
163
|
+
};
|
|
164
|
+
if (firstEvents.length) {
|
|
165
|
+
for (const event of firstEvents) {
|
|
166
|
+
createdFunnels.push({ ...clone(funnelTemplate), sequence: [event], isFirstFunnel: true, conversionRate: 100 });
|
|
119
167
|
}
|
|
120
|
-
|
|
121
|
-
}, [])
|
|
168
|
+
}
|
|
122
169
|
|
|
123
|
-
//
|
|
124
|
-
.
|
|
170
|
+
//at least one funnel with all usage events
|
|
171
|
+
createdFunnels.push({ ...clone(funnelTemplate), sequence: usageEvents });
|
|
172
|
+
|
|
173
|
+
//for the rest, make random funnels
|
|
174
|
+
followUpFunnels: for (let i = 1; i < numFunnelsToCreate; i++) {
|
|
175
|
+
/** @type {Funnel} */
|
|
176
|
+
const funnel = { ...clone(funnelTemplate) };
|
|
177
|
+
funnel.conversionRate = u.integer(25, 75);
|
|
178
|
+
funnel.timeToConvert = u.integer(1, 10);
|
|
179
|
+
funnel.weight = u.integer(1, 10);
|
|
180
|
+
const sequence = u.shuffleArray(usageEvents).slice(0, u.integer(2, usageEvents.length));
|
|
181
|
+
funnel.sequence = sequence;
|
|
182
|
+
funnel.order = 'random';
|
|
183
|
+
createdFunnels.push(funnel);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
funnels = createdFunnels;
|
|
187
|
+
config.funnels = funnels;
|
|
188
|
+
CONFIG = config;
|
|
125
189
|
|
|
126
|
-
const firstEvents = events.filter((e) => e.isFirstEvent);
|
|
127
|
-
const eventData = enrichArray([], { hook, type: "event", config });
|
|
128
|
-
const userProfilesData = enrichArray([], { hook, type: "user", config });
|
|
129
|
-
const scdTableKeys = Object.keys(scdProps);
|
|
130
|
-
const scdTableData = [];
|
|
131
|
-
for (const [index, key] of scdTableKeys.entries()) {
|
|
132
|
-
scdTableData[index] = enrichArray([], { hook, type: "scd", config, scdKey: key });
|
|
133
190
|
}
|
|
134
|
-
// const scdTableData = enrichArray([], { hook, type: "scd", config });
|
|
135
|
-
const groupProfilesData = enrichArray([], { hook, type: "groups", config });
|
|
136
|
-
const lookupTableData = enrichArray([], { hook, type: "lookups", config });
|
|
137
|
-
const avgEvPerUser = Math.floor(numEvents / numUsers);
|
|
138
191
|
|
|
139
192
|
//user loop
|
|
140
193
|
log(`---------------SIMULATION----------------`, "\n\n");
|
|
141
|
-
for (let i = 1; i < numUsers + 1; i++) {
|
|
194
|
+
loopUsers: for (let i = 1; i < numUsers + 1; i++) {
|
|
142
195
|
u.progress("users", i);
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
196
|
+
const userId = chance.guid();
|
|
197
|
+
// const user = u.generateUser(userId, numDays, amp, freq, skew);
|
|
198
|
+
const user = u.generateUser(userId, numDays);
|
|
199
|
+
const { distinct_id, created, anonymousIds, sessionIds } = user;
|
|
200
|
+
let numEventsPreformed = 0;
|
|
201
|
+
|
|
202
|
+
// profile creation
|
|
203
|
+
const profile = makeProfile(userProps, user);
|
|
204
|
+
userProfilesData.hookPush(profile);
|
|
205
|
+
|
|
206
|
+
//scd creation
|
|
207
|
+
/** @type {Record<string, SCDTableRow[]>} */
|
|
208
|
+
// @ts-ignore
|
|
209
|
+
const userSCD = {};
|
|
148
210
|
for (const [index, key] of scdTableKeys.entries()) {
|
|
149
211
|
const mutations = chance.integer({ min: 1, max: 10 });
|
|
150
|
-
|
|
212
|
+
const changes = makeSCD(scdProps[key], key, distinct_id, mutations, created);
|
|
213
|
+
// @ts-ignore
|
|
214
|
+
userSCD[key] = changes;
|
|
215
|
+
scdTableData[index].hookPush(changes);
|
|
151
216
|
}
|
|
152
217
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
218
|
+
let numEventsThisUserWillPreform = Math.floor(chance.normal({
|
|
219
|
+
mean: avgEvPerUser,
|
|
220
|
+
dev: avgEvPerUser / u.integer(u.integer(2, 5), u.integer(2, 7))
|
|
221
|
+
}) * 0.714159265359);
|
|
156
222
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
223
|
+
// power users do 5x more events
|
|
224
|
+
chance.bool({ likelihood: 20 }) ? numEventsThisUserWillPreform *= 5 : null;
|
|
225
|
+
|
|
226
|
+
// shitty users do 1/3 as many events
|
|
227
|
+
chance.bool({ likelihood: 15 }) ? numEventsThisUserWillPreform *= 0.333 : null;
|
|
228
|
+
|
|
229
|
+
numEventsThisUserWillPreform = Math.round(numEventsThisUserWillPreform);
|
|
230
|
+
|
|
231
|
+
let userFirstEventTime;
|
|
232
|
+
|
|
233
|
+
//first funnel
|
|
234
|
+
const firstFunnels = funnels.filter((f) => f.isFirstFunnel).reduce(u.weighFunnels, []);
|
|
235
|
+
const usageFunnels = funnels.filter((f) => !f.isFirstFunnel).reduce(u.weighFunnels, []);
|
|
236
|
+
const userIsBornInDataset = chance.bool({ likelihood: 30 });
|
|
237
|
+
if (firstFunnels.length && userIsBornInDataset) {
|
|
238
|
+
/** @type {Funnel} */
|
|
239
|
+
const firstFunnel = chance.pickone(firstFunnels, user);
|
|
240
|
+
|
|
241
|
+
const [data, userConverted] = makeFunnel(firstFunnel, user, profile, userSCD, null, config);
|
|
242
|
+
userFirstEventTime = dayjs(data[0].time).unix();
|
|
243
|
+
numEventsPreformed += data.length;
|
|
244
|
+
eventData.hookPush(data);
|
|
245
|
+
if (!userConverted) continue loopUsers;
|
|
170
246
|
}
|
|
171
247
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
weightedEvents,
|
|
181
|
-
superProps,
|
|
182
|
-
groupKeys
|
|
183
|
-
)
|
|
184
|
-
);
|
|
248
|
+
while (numEventsPreformed < numEventsThisUserWillPreform) {
|
|
249
|
+
if (usageFunnels.length) {
|
|
250
|
+
/** @type {Funnel} */
|
|
251
|
+
const currentFunnel = chance.pickone(usageFunnels);
|
|
252
|
+
const [data, userConverted] = makeFunnel(currentFunnel, user, profile, userSCD, userFirstEventTime, config);
|
|
253
|
+
numEventsPreformed += data.length;
|
|
254
|
+
eventData.hookPush(data);
|
|
255
|
+
}
|
|
185
256
|
}
|
|
257
|
+
// end individual user loop
|
|
186
258
|
}
|
|
187
259
|
|
|
188
|
-
//flatten SCD
|
|
260
|
+
//flatten SCD tables
|
|
189
261
|
scdTableData.forEach((table, index) => scdTableData[index] = table.flat());
|
|
190
262
|
|
|
191
263
|
log("\n");
|
|
@@ -202,9 +274,10 @@ async function main(config) {
|
|
|
202
274
|
...makeProfile(groupProps[groupKey]),
|
|
203
275
|
// $distinct_id: i,
|
|
204
276
|
};
|
|
277
|
+
group["distinct_id"] = i;
|
|
205
278
|
groupProfiles.push(group);
|
|
206
279
|
}
|
|
207
|
-
groupProfilesData.
|
|
280
|
+
groupProfilesData.hookPush({ key: groupKey, data: groupProfiles });
|
|
208
281
|
}
|
|
209
282
|
log("\n");
|
|
210
283
|
|
|
@@ -220,10 +293,36 @@ async function main(config) {
|
|
|
220
293
|
};
|
|
221
294
|
data.push(item);
|
|
222
295
|
}
|
|
223
|
-
lookupTableData.
|
|
296
|
+
lookupTableData.hookPush({ key, data });
|
|
224
297
|
}
|
|
225
298
|
|
|
226
|
-
//
|
|
299
|
+
// SHIFT TIME
|
|
300
|
+
const actualNow = dayjs();
|
|
301
|
+
const fixedNow = dayjs.unix(global.NOW);
|
|
302
|
+
const timeShift = actualNow.diff(fixedNow, "second");
|
|
303
|
+
const dayShift = actualNow.diff(global.NOW, "day");
|
|
304
|
+
eventData.forEach((event) => {
|
|
305
|
+
const newTime = dayjs(event.time).add(timeShift, "second");
|
|
306
|
+
event.time = newTime.toISOString();
|
|
307
|
+
if (epochStart && newTime.unix() < epochStart) event = {};
|
|
308
|
+
if (epochEnd && newTime.unix() > epochEnd) event = {};
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
userProfilesData.forEach((profile) => {
|
|
312
|
+
const newTime = dayjs(profile.created).add(dayShift, "day");
|
|
313
|
+
profile.created = newTime.toISOString();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
// draw charts
|
|
318
|
+
if (makeChart) {
|
|
319
|
+
const bornEvents = config.events?.filter((e) => e.isFirstEvent)?.map(e => e.event) || [];
|
|
320
|
+
const bornFunnels = config.funnels?.filter((f) => f.isFirstFunnel)?.map(f => f.sequence[0]) || [];
|
|
321
|
+
const bornBehaviors = [...bornEvents, ...bornFunnels];
|
|
322
|
+
const chart = await generateLineChart(eventData, bornBehaviors, makeChart);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// create mirrorProps
|
|
227
326
|
let mirrorEventData = [];
|
|
228
327
|
const mirrorPropKeys = Object.keys(mirrorProps);
|
|
229
328
|
if (mirrorPropKeys.length) {
|
|
@@ -237,7 +336,7 @@ async function main(config) {
|
|
|
237
336
|
}
|
|
238
337
|
|
|
239
338
|
const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, mirrorFiles, folder } =
|
|
240
|
-
buildFileNames(config);
|
|
339
|
+
u.buildFileNames(config);
|
|
241
340
|
const pairs = [
|
|
242
341
|
[eventFiles, [eventData]],
|
|
243
342
|
[userFiles, [userProfilesData]],
|
|
@@ -250,20 +349,7 @@ async function main(config) {
|
|
|
250
349
|
log(`---------------SIMULATION----------------`, "\n");
|
|
251
350
|
|
|
252
351
|
if (!writeToDisk && !token) {
|
|
253
|
-
track('end simulation',
|
|
254
|
-
runId,
|
|
255
|
-
seed,
|
|
256
|
-
numEvents,
|
|
257
|
-
numUsers,
|
|
258
|
-
numDays,
|
|
259
|
-
anonIds,
|
|
260
|
-
sessionIds,
|
|
261
|
-
format,
|
|
262
|
-
token,
|
|
263
|
-
region,
|
|
264
|
-
writeToDisk,
|
|
265
|
-
isCLI
|
|
266
|
-
});
|
|
352
|
+
track('end simulation', trackingParams);
|
|
267
353
|
return {
|
|
268
354
|
eventData,
|
|
269
355
|
userProfilesData,
|
|
@@ -271,7 +357,7 @@ async function main(config) {
|
|
|
271
357
|
groupProfilesData,
|
|
272
358
|
lookupTableData,
|
|
273
359
|
mirrorEventData,
|
|
274
|
-
|
|
360
|
+
importResults: {},
|
|
275
361
|
files: []
|
|
276
362
|
};
|
|
277
363
|
}
|
|
@@ -315,30 +401,28 @@ async function main(config) {
|
|
|
315
401
|
const creds = { token };
|
|
316
402
|
/** @type {import('mixpanel-import').Options} */
|
|
317
403
|
const commonOpts = {
|
|
318
|
-
|
|
319
404
|
region,
|
|
320
405
|
fixData: true,
|
|
321
406
|
verbose: false,
|
|
322
407
|
forceStream: true,
|
|
323
|
-
strict:
|
|
408
|
+
strict: true,
|
|
324
409
|
dryRun: false,
|
|
325
410
|
abridged: false,
|
|
411
|
+
fixJson: true,
|
|
412
|
+
showProgress: true
|
|
326
413
|
};
|
|
327
414
|
|
|
328
415
|
if (eventData) {
|
|
329
|
-
log(`importing events to mixpanel
|
|
416
|
+
log(`importing events to mixpanel...\n`);
|
|
330
417
|
const imported = await mp(creds, eventData, {
|
|
331
418
|
recordType: "event",
|
|
332
|
-
fixData: true,
|
|
333
|
-
fixJson: true,
|
|
334
|
-
strict: false,
|
|
335
419
|
...commonOpts,
|
|
336
420
|
});
|
|
337
421
|
log(`\tsent ${comma(imported.success)} events\n`);
|
|
338
422
|
importResults.events = imported;
|
|
339
423
|
}
|
|
340
424
|
if (userProfilesData) {
|
|
341
|
-
log(`importing user profiles to mixpanel
|
|
425
|
+
log(`importing user profiles to mixpanel...\n`);
|
|
342
426
|
const imported = await mp(creds, userProfilesData, {
|
|
343
427
|
recordType: "user",
|
|
344
428
|
...commonOpts,
|
|
@@ -350,138 +434,76 @@ async function main(config) {
|
|
|
350
434
|
for (const groupProfiles of groupProfilesData) {
|
|
351
435
|
const groupKey = groupProfiles.key;
|
|
352
436
|
const data = groupProfiles.data;
|
|
353
|
-
log(`importing ${groupKey} profiles to mixpanel
|
|
437
|
+
log(`importing ${groupKey} profiles to mixpanel...\n`);
|
|
354
438
|
const imported = await mp({ token, groupKey }, data, {
|
|
355
439
|
recordType: "group",
|
|
356
440
|
...commonOpts,
|
|
441
|
+
|
|
357
442
|
});
|
|
358
443
|
log(`\tsent ${comma(imported.success)} ${groupKey} profiles\n`);
|
|
359
444
|
|
|
360
445
|
importResults.groups.push(imported);
|
|
361
446
|
}
|
|
362
447
|
}
|
|
363
|
-
|
|
364
448
|
}
|
|
365
449
|
log(`\n-----------------WRITES------------------`, "\n");
|
|
366
|
-
track('end simulation',
|
|
367
|
-
|
|
368
|
-
seed,
|
|
369
|
-
numEvents,
|
|
370
|
-
numUsers,
|
|
371
|
-
numDays,
|
|
372
|
-
events,
|
|
373
|
-
anonIds,
|
|
374
|
-
sessionIds,
|
|
375
|
-
format,
|
|
376
|
-
targetToken: token,
|
|
377
|
-
region,
|
|
378
|
-
writeToDisk,
|
|
379
|
-
isCLI,
|
|
380
|
-
version
|
|
381
|
-
});
|
|
450
|
+
track('end simulation', trackingParams);
|
|
451
|
+
|
|
382
452
|
return {
|
|
383
|
-
|
|
453
|
+
importResults,
|
|
384
454
|
files: [eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, mirrorFiles, folder],
|
|
455
|
+
eventData,
|
|
456
|
+
userProfilesData,
|
|
457
|
+
scdTableData,
|
|
458
|
+
groupProfilesData,
|
|
459
|
+
lookupTableData,
|
|
460
|
+
mirrorEventData,
|
|
385
461
|
};
|
|
386
462
|
}
|
|
387
463
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
function makeProfile(props, defaults) {
|
|
392
|
-
//build the spec
|
|
393
|
-
const profile = {
|
|
394
|
-
...defaults,
|
|
395
|
-
};
|
|
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
|
-
|
|
401
|
-
for (const key in props) {
|
|
402
|
-
try {
|
|
403
|
-
profile[key] = u.choose(props[key]);
|
|
404
|
-
} catch (e) {
|
|
405
|
-
// debugger;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return profile;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* @param {import('./types.d.ts').ValueValid} prop
|
|
413
|
-
* @param {string} scdKey
|
|
414
|
-
* @param {string} distinct_id
|
|
415
|
-
* @param {number} mutations
|
|
416
|
-
* @param {string} $created
|
|
417
|
-
*/
|
|
418
|
-
function makeSCD(prop, scdKey, distinct_id, mutations, $created) {
|
|
419
|
-
if (JSON.stringify(prop) === "{}") return {};
|
|
420
|
-
if (JSON.stringify(prop) === "[]") return [];
|
|
421
|
-
const scdEntries = [];
|
|
422
|
-
let lastInserted = dayjs($created);
|
|
423
|
-
const deltaDays = dayjs().diff(lastInserted, "day");
|
|
424
|
-
|
|
425
|
-
for (let i = 0; i < mutations; i++) {
|
|
426
|
-
if (lastInserted.isAfter(dayjs())) break;
|
|
427
|
-
const scd = makeProfile({ [scdKey]: prop }, { distinct_id });
|
|
428
|
-
scd.startTime = lastInserted.toISOString();
|
|
429
|
-
lastInserted = lastInserted.add(u.integer(1, 1000), "seconds");
|
|
430
|
-
scd.insertTime = lastInserted.toISOString();
|
|
431
|
-
scdEntries.push({ ...scd });
|
|
432
|
-
lastInserted = lastInserted
|
|
433
|
-
.add(u.integer(0, deltaDays), "day")
|
|
434
|
-
.subtract(u.integer(1, 1000), "seconds");
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return scdEntries;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
464
|
/**
|
|
441
465
|
* creates a random event
|
|
442
466
|
* @param {string} distinct_id
|
|
443
467
|
* @param {string[]} anonymousIds
|
|
444
468
|
* @param {string[]} sessionIds
|
|
445
469
|
* @param {number} earliestTime
|
|
446
|
-
* @param {
|
|
470
|
+
* @param {EventConfig} chosenEvent
|
|
447
471
|
* @param {Object} superProps
|
|
448
472
|
* @param {Object} groupKeys
|
|
449
473
|
* @param {Boolean} isFirstEvent=false
|
|
450
474
|
*/
|
|
451
|
-
function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
//allow for a string shorthand
|
|
455
|
-
if (typeof chosenEvent === "string") {
|
|
456
|
-
chosenEvent = { event: chosenEvent, properties: {} };
|
|
457
|
-
}
|
|
458
|
-
|
|
475
|
+
function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEvent, superProps, groupKeys, isFirstEvent = false) {
|
|
476
|
+
const { mean = 0, dev = 2, peaks = 5 } = CONFIG.soup;
|
|
459
477
|
//event model
|
|
460
|
-
const
|
|
478
|
+
const eventTemplate = {
|
|
461
479
|
event: chosenEvent.event,
|
|
462
|
-
|
|
480
|
+
source: "dm4",
|
|
463
481
|
};
|
|
464
482
|
|
|
465
483
|
//event time
|
|
466
|
-
if (
|
|
467
|
-
|
|
484
|
+
if (earliestTime > NOW) {
|
|
485
|
+
earliestTime = dayjs.unix(NOW).subtract(2, 'd').unix();
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
if (isFirstEvent) eventTemplate.time = dayjs.unix(earliestTime).toISOString();
|
|
489
|
+
if (!isFirstEvent) eventTemplate.time = u.TimeSoup(earliestTime, NOW, peaks, dev, mean);
|
|
468
490
|
|
|
469
491
|
// anonymous and session ids
|
|
470
|
-
if (
|
|
471
|
-
if (
|
|
492
|
+
if (CONFIG?.anonIds) eventTemplate.device_id = CONFIG.chance.pickone(anonymousIds);
|
|
493
|
+
if (CONFIG?.sessionIds) eventTemplate.session_id = CONFIG.chance.pickone(sessionIds);
|
|
472
494
|
|
|
473
|
-
//sometimes have a
|
|
474
|
-
if (!isFirstEvent && chance.bool({ likelihood: 42 }))
|
|
495
|
+
//sometimes have a user_id
|
|
496
|
+
if (!isFirstEvent && CONFIG.chance.bool({ likelihood: 42 })) eventTemplate.user_id = distinct_id;
|
|
475
497
|
|
|
476
|
-
// ensure that there is a
|
|
477
|
-
if (!
|
|
498
|
+
// ensure that there is a user_id or device_id
|
|
499
|
+
if (!eventTemplate.user_id && !eventTemplate.device_id) eventTemplate.user_id = distinct_id;
|
|
478
500
|
|
|
479
501
|
const props = { ...chosenEvent.properties, ...superProps };
|
|
480
502
|
|
|
481
503
|
//iterate through custom properties
|
|
482
504
|
for (const key in props) {
|
|
483
505
|
try {
|
|
484
|
-
|
|
506
|
+
eventTemplate[key] = u.choose(props[key]);
|
|
485
507
|
} catch (e) {
|
|
486
508
|
console.error(`error with ${key} in ${chosenEvent.event} event`, e);
|
|
487
509
|
debugger;
|
|
@@ -495,99 +517,220 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
|
|
|
495
517
|
const groupEvents = groupPair[2] || [];
|
|
496
518
|
|
|
497
519
|
// empty array for group events means all events
|
|
498
|
-
if (!groupEvents.length)
|
|
499
|
-
if (groupEvents.includes(
|
|
520
|
+
if (!groupEvents.length) eventTemplate[groupKey] = u.pick(u.weightedRange(1, groupCardinality));
|
|
521
|
+
if (groupEvents.includes(eventTemplate.event)) eventTemplate[groupKey] = u.pick(u.weightedRange(1, groupCardinality));
|
|
500
522
|
}
|
|
501
523
|
|
|
502
524
|
//make $insert_id
|
|
503
|
-
|
|
525
|
+
eventTemplate.insert_id = md5(JSON.stringify(eventTemplate));
|
|
504
526
|
|
|
505
|
-
return
|
|
527
|
+
return eventTemplate;
|
|
506
528
|
}
|
|
507
529
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
};
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
530
|
+
/**
|
|
531
|
+
* creates a funnel of events for a user
|
|
532
|
+
* this is called multiple times for a user
|
|
533
|
+
* @param {Funnel} funnel
|
|
534
|
+
* @param {Person} user
|
|
535
|
+
* @param {UserProfile} profile
|
|
536
|
+
* @param {Record<string, SCDTableRow[]>} scd
|
|
537
|
+
* @param {number} firstEventTime
|
|
538
|
+
* @param {Config} config
|
|
539
|
+
* @return {[EventSpec[], Boolean]}
|
|
540
|
+
*/
|
|
541
|
+
function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
542
|
+
const { hook } = config;
|
|
543
|
+
hook(funnel, "funnel-pre", { user, profile, scd, funnel, config });
|
|
544
|
+
const { sequence, conversionRate = 50, order = 'sequential', timeToConvert = 1, props } = funnel;
|
|
545
|
+
const { distinct_id, created, anonymousIds, sessionIds } = user;
|
|
546
|
+
const { superProps, groupKeys } = config;
|
|
547
|
+
const { name, email } = profile;
|
|
548
|
+
|
|
549
|
+
const chosenFunnelProps = { ...props, ...superProps };
|
|
550
|
+
for (const key in props) {
|
|
551
|
+
try {
|
|
552
|
+
chosenFunnelProps[key] = u.choose(chosenFunnelProps[key]);
|
|
553
|
+
} catch (e) {
|
|
554
|
+
console.error(`error with ${key} in ${funnel.sequence.join(" > ")} funnel`, e);
|
|
555
|
+
debugger;
|
|
556
|
+
}
|
|
535
557
|
}
|
|
536
558
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
559
|
+
const funnelPossibleEvents = sequence.map((event) => {
|
|
560
|
+
const foundEvent = config.events.find((e) => e.event === event);
|
|
561
|
+
/** @type {EventConfig} */
|
|
562
|
+
const eventSpec = foundEvent || { event, properties: {} };
|
|
563
|
+
for (const key in eventSpec.properties) {
|
|
564
|
+
try {
|
|
565
|
+
eventSpec.properties[key] = u.choose(eventSpec.properties[key]);
|
|
566
|
+
} catch (e) {
|
|
567
|
+
console.error(`error with ${key} in ${eventSpec.event} event`, e);
|
|
568
|
+
debugger;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
delete eventSpec.isFirstEvent;
|
|
572
|
+
delete eventSpec.weight;
|
|
573
|
+
eventSpec.properties = { ...eventSpec.properties, ...chosenFunnelProps };
|
|
574
|
+
return eventSpec;
|
|
575
|
+
});
|
|
540
576
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
577
|
+
const doesUserConvert = config.chance.bool({ likelihood: conversionRate });
|
|
578
|
+
let numStepsUserWillTake = sequence.length;
|
|
579
|
+
if (!doesUserConvert) numStepsUserWillTake = u.integer(1, sequence.length - 1);
|
|
580
|
+
const funnelTotalRelativeTimeInHours = timeToConvert / numStepsUserWillTake;
|
|
581
|
+
const msInHour = 60000 * 60;
|
|
582
|
+
|
|
583
|
+
let lastTimeJump = 0;
|
|
584
|
+
const funnelActualEvents = funnelPossibleEvents.slice(0, numStepsUserWillTake)
|
|
585
|
+
.map((event, index) => {
|
|
586
|
+
if (index === 0) {
|
|
587
|
+
event.relativeTimeMs = 0;
|
|
588
|
+
return event;
|
|
589
|
+
}
|
|
545
590
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const { key } = lookupTable;
|
|
549
|
-
writePaths.lookupFiles.push(
|
|
550
|
-
//lookups are always CSVs
|
|
551
|
-
path.join(writeDir, `${simName}-${key}-LOOKUP.csv`)
|
|
552
|
-
);
|
|
553
|
-
}
|
|
591
|
+
// Calculate base increment for each step
|
|
592
|
+
const baseIncrement = (timeToConvert * msInHour) / numStepsUserWillTake;
|
|
554
593
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
594
|
+
// Introduce a random fluctuation factor
|
|
595
|
+
const fluctuation = u.integer(-baseIncrement / u.integer(3, 5), baseIncrement / u.integer(3, 5));
|
|
596
|
+
|
|
597
|
+
// Ensure the time increments are increasing and add randomness
|
|
598
|
+
const previousTime = lastTimeJump;
|
|
599
|
+
const currentTime = previousTime + baseIncrement + fluctuation;
|
|
600
|
+
|
|
601
|
+
// Assign the calculated time to the event
|
|
602
|
+
const chosenTime = Math.max(currentTime, previousTime + 1); // Ensure non-decreasing time
|
|
603
|
+
lastTimeJump = chosenTime;
|
|
604
|
+
event.relativeTimeMs = chosenTime;
|
|
605
|
+
return event;
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
let funnelActualOrder = [];
|
|
610
|
+
|
|
611
|
+
//todo
|
|
612
|
+
switch (order) {
|
|
613
|
+
case "sequential":
|
|
614
|
+
funnelActualOrder = funnelActualEvents;
|
|
615
|
+
break;
|
|
616
|
+
case "random":
|
|
617
|
+
funnelActualOrder = u.shuffleArray(funnelActualEvents);
|
|
618
|
+
break;
|
|
619
|
+
case "first-fixed":
|
|
620
|
+
funnelActualOrder = u.shuffleExceptFirst(funnelActualEvents);
|
|
621
|
+
break;
|
|
622
|
+
case "last-fixed":
|
|
623
|
+
funnelActualOrder = u.shuffleExceptLast(funnelActualEvents);
|
|
624
|
+
break;
|
|
625
|
+
case "first-and-last-fixed":
|
|
626
|
+
funnelActualOrder = u.fixFirstAndLast(funnelActualEvents);
|
|
627
|
+
break;
|
|
628
|
+
case "middle-fixed":
|
|
629
|
+
funnelActualOrder = u.shuffleOutside(funnelActualEvents);
|
|
630
|
+
break;
|
|
631
|
+
default:
|
|
632
|
+
funnelActualOrder = funnelActualEvents;
|
|
633
|
+
break;
|
|
561
634
|
}
|
|
562
635
|
|
|
563
|
-
|
|
636
|
+
const earliestTime = firstEventTime || dayjs(created).unix();
|
|
637
|
+
let funnelStartTime;
|
|
638
|
+
let finalEvents = funnelActualOrder
|
|
639
|
+
.map((event, index) => {
|
|
640
|
+
const newEvent = makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, event, {}, groupKeys);
|
|
641
|
+
if (index === 0) {
|
|
642
|
+
funnelStartTime = dayjs(newEvent.time);
|
|
643
|
+
delete newEvent.relativeTimeMs;
|
|
644
|
+
return newEvent;
|
|
645
|
+
}
|
|
646
|
+
newEvent.time = dayjs(funnelStartTime).add(event.relativeTimeMs, "milliseconds").toISOString();
|
|
647
|
+
delete newEvent.relativeTimeMs;
|
|
648
|
+
return newEvent;
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
hook(finalEvents, "funnel-post", { user, profile, scd, funnel, config });
|
|
653
|
+
return [finalEvents, doesUserConvert];
|
|
564
654
|
}
|
|
565
655
|
|
|
566
|
-
/** @typedef {import('./types').EnrichedArray} EnrichArray */
|
|
567
|
-
/** @typedef {import('./types').EnrichArrayOptions} EnrichArrayOptions */
|
|
568
656
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
657
|
+
function makeProfile(props, defaults) {
|
|
658
|
+
//build the spec
|
|
659
|
+
const profile = {
|
|
660
|
+
...defaults,
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
// anonymous and session ids
|
|
664
|
+
if (!CONFIG?.anonIds) delete profile.anonymousIds;
|
|
665
|
+
if (!CONFIG?.sessionIds) delete profile.sessionIds;
|
|
666
|
+
|
|
667
|
+
for (const key in props) {
|
|
668
|
+
try {
|
|
669
|
+
profile[key] = u.choose(props[key]);
|
|
670
|
+
} catch (e) {
|
|
671
|
+
// debugger;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return profile;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @param {import('./types.d.ts').ValueValid} prop
|
|
680
|
+
* @param {string} scdKey
|
|
681
|
+
* @param {string} distinct_id
|
|
682
|
+
* @param {number} mutations
|
|
683
|
+
* @param {string} created
|
|
573
684
|
*/
|
|
574
|
-
function
|
|
575
|
-
|
|
685
|
+
function makeSCD(prop, scdKey, distinct_id, mutations, created) {
|
|
686
|
+
if (JSON.stringify(prop) === "{}") return {};
|
|
687
|
+
if (JSON.stringify(prop) === "[]") return [];
|
|
688
|
+
const scdEntries = [];
|
|
689
|
+
let lastInserted = dayjs(created);
|
|
690
|
+
const deltaDays = dayjs().diff(lastInserted, "day");
|
|
576
691
|
|
|
577
|
-
|
|
578
|
-
|
|
692
|
+
for (let i = 0; i < mutations; i++) {
|
|
693
|
+
if (lastInserted.isAfter(dayjs())) break;
|
|
694
|
+
const scd = makeProfile({ [scdKey]: prop }, { distinct_id });
|
|
695
|
+
scd.startTime = lastInserted.toISOString();
|
|
696
|
+
lastInserted = lastInserted.add(u.integer(1, 1000), "seconds");
|
|
697
|
+
scd.insertTime = lastInserted.toISOString();
|
|
698
|
+
scdEntries.push({ ...scd });
|
|
699
|
+
lastInserted = lastInserted
|
|
700
|
+
.add(u.integer(0, deltaDays), "day")
|
|
701
|
+
.subtract(u.integer(1, 1000), "seconds");
|
|
579
702
|
}
|
|
580
703
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
704
|
+
return scdEntries;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* @param {EventConfig[] | string[]} events
|
|
709
|
+
*/
|
|
710
|
+
function validateEvents(events) {
|
|
711
|
+
if (!Array.isArray(events)) throw new Error("events must be an array");
|
|
712
|
+
const cleanEventConfig = [];
|
|
713
|
+
for (const event of events) {
|
|
714
|
+
if (typeof event === "string") {
|
|
715
|
+
/** @type {EventConfig} */
|
|
716
|
+
const eventTemplate = {
|
|
717
|
+
event,
|
|
718
|
+
isFirstEvent: false,
|
|
719
|
+
properties: {},
|
|
720
|
+
weight: u.integer(1, 5)
|
|
721
|
+
};
|
|
722
|
+
cleanEventConfig.push(eventTemplate);
|
|
723
|
+
}
|
|
724
|
+
if (typeof event === "object") {
|
|
725
|
+
cleanEventConfig.push(event);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return cleanEventConfig;
|
|
729
|
+
}
|
|
730
|
+
|
|
584
731
|
|
|
585
732
|
|
|
586
|
-
enrichedArray.hPush = transformThenPush;
|
|
587
|
-
|
|
588
733
|
|
|
589
|
-
return enrichedArray;
|
|
590
|
-
};
|
|
591
734
|
|
|
592
735
|
|
|
593
736
|
|
|
@@ -596,12 +739,11 @@ if (require.main === module) {
|
|
|
596
739
|
isCLI = true;
|
|
597
740
|
const args = cliParams();
|
|
598
741
|
// @ts-ignore
|
|
599
|
-
|
|
742
|
+
let { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk, complex = false, sessionIds, anonIds } = args;
|
|
600
743
|
// @ts-ignore
|
|
601
744
|
const suppliedConfig = args._[0];
|
|
602
745
|
|
|
603
|
-
//if the user
|
|
604
|
-
//todo this text isn't displaying
|
|
746
|
+
//if the user specifies an separate config file
|
|
605
747
|
let config = null;
|
|
606
748
|
if (suppliedConfig) {
|
|
607
749
|
console.log(`using ${suppliedConfig} for data\n`);
|
|
@@ -612,18 +754,19 @@ if (require.main === module) {
|
|
|
612
754
|
console.log(`... using default COMPLEX configuration [everything] ...\n`);
|
|
613
755
|
console.log(`... for more simple data, don't use the --complex flag ...\n`);
|
|
614
756
|
console.log(`... or specify your own js config file (see docs or --help) ...\n`);
|
|
615
|
-
config = require(path.resolve(__dirname, "./
|
|
757
|
+
config = require(path.resolve(__dirname, "./schemas/complex.js"));
|
|
616
758
|
}
|
|
617
759
|
else {
|
|
618
760
|
console.log(`... using default SIMPLE configuration [events + users] ...\n`);
|
|
619
761
|
console.log(`... for more complex data, use the --complex flag ...\n`);
|
|
620
|
-
config = require(path.resolve(__dirname, "./
|
|
762
|
+
config = require(path.resolve(__dirname, "./schemas/simple.js"));
|
|
621
763
|
}
|
|
622
764
|
}
|
|
623
765
|
|
|
624
766
|
//override config with cli params
|
|
625
767
|
if (token) config.token = token;
|
|
626
768
|
if (seed) config.seed = seed;
|
|
769
|
+
if (format === "csv" && config.format === "json") format = "json";
|
|
627
770
|
if (format) config.format = format;
|
|
628
771
|
if (numDays) config.numDays = numDays;
|
|
629
772
|
if (numUsers) config.numUsers = numUsers;
|
|
@@ -640,7 +783,7 @@ if (require.main === module) {
|
|
|
640
783
|
log(`-----------------SUMMARY-----------------`);
|
|
641
784
|
const d = { success: 0, bytes: 0 };
|
|
642
785
|
const darr = [d];
|
|
643
|
-
const { events = d, groups = darr, users = d } = data.
|
|
786
|
+
const { events = d, groups = darr, users = d } = data.importResults;
|
|
644
787
|
const files = data.files;
|
|
645
788
|
const folder = files?.pop();
|
|
646
789
|
const groupBytes = groups.reduce((acc, group) => {
|
|
@@ -673,7 +816,6 @@ if (require.main === module) {
|
|
|
673
816
|
});
|
|
674
817
|
} else {
|
|
675
818
|
main.utils = { ...u };
|
|
676
|
-
main.timeSoup = AKsTimeSoup;
|
|
677
819
|
module.exports = main;
|
|
678
820
|
}
|
|
679
821
|
|