@naturalcycles/abba 1.23.1 → 1.25.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/dist/abba.d.ts +1 -1
- package/dist/abba.js +51 -24
- package/dist/dao/experiment.dao.d.ts +1 -0
- package/dist/dao/experiment.dao.js +2 -0
- package/dist/migrations/init.sql +1 -0
- package/dist/types.d.ts +9 -5
- package/package.json +1 -1
- package/src/abba.ts +56 -25
- package/src/dao/experiment.dao.ts +3 -0
- package/src/migrations/init.sql +1 -0
- package/src/types.ts +10 -4
package/dist/abba.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export declare class Abba {
|
|
|
48
48
|
* @param existingOnly Do not generate any new assignments for this experiment
|
|
49
49
|
* @param segmentationData Required if existingOnly is false
|
|
50
50
|
*/
|
|
51
|
-
getUserAssignment(experimentKey: string, userId: string, existingOnly: boolean, segmentationData?: SegmentationData): Promise<GeneratedUserAssignment
|
|
51
|
+
getUserAssignment(experimentKey: string, userId: string, existingOnly: boolean, segmentationData?: SegmentationData): Promise<GeneratedUserAssignment>;
|
|
52
52
|
/**
|
|
53
53
|
* Get all existing user assignments.
|
|
54
54
|
* Hot method.
|
package/dist/abba.js
CHANGED
|
@@ -129,38 +129,56 @@ class Abba {
|
|
|
129
129
|
(0, js_lib_1._assert)(experiment, `Experiment does not exist: ${experimentKey}`);
|
|
130
130
|
// Inactive experiments should never return an assignment
|
|
131
131
|
if (experiment.status === types_1.AssignmentStatus.Inactive)
|
|
132
|
-
return
|
|
132
|
+
return {
|
|
133
|
+
experiment,
|
|
134
|
+
assignment: null,
|
|
135
|
+
};
|
|
133
136
|
const buckets = await this.bucketDao.getBy('experimentId', experiment.id);
|
|
134
137
|
const existingAssignments = await this.userAssignmentDao.getBy('userId', userId);
|
|
135
138
|
const existing = existingAssignments.find(a => a.experimentId === experiment.id);
|
|
136
139
|
if (existing) {
|
|
137
140
|
const bucket = buckets.find(b => b.id === existing.bucketId);
|
|
138
141
|
return {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
experiment,
|
|
143
|
+
assignment: {
|
|
144
|
+
...existing,
|
|
145
|
+
experimentKey: experiment.key,
|
|
146
|
+
bucketKey: bucket?.key || null,
|
|
147
|
+
bucketData: bucket?.data || null,
|
|
148
|
+
},
|
|
143
149
|
};
|
|
144
150
|
}
|
|
145
151
|
// No existing assignment, but we don't want to generate a new one
|
|
146
152
|
if (existingOnly || experiment.status === types_1.AssignmentStatus.Paused)
|
|
147
|
-
return
|
|
153
|
+
return {
|
|
154
|
+
experiment,
|
|
155
|
+
assignment: null,
|
|
156
|
+
};
|
|
148
157
|
const experiments = await this.getAllExperiments();
|
|
149
158
|
const exclusionSet = (0, util_1.getUserExclusionSet)(experiments, existingAssignments);
|
|
150
159
|
if (!(0, util_1.canGenerateNewAssignments)(experiment, exclusionSet))
|
|
151
|
-
return
|
|
160
|
+
return {
|
|
161
|
+
experiment,
|
|
162
|
+
assignment: null,
|
|
163
|
+
};
|
|
152
164
|
(0, js_lib_1._assert)(segmentationData, 'Segmentation data required when creating a new assignment');
|
|
153
165
|
const experimentWithBuckets = { ...experiment, buckets };
|
|
154
166
|
const assignment = (0, util_1.generateUserAssignmentData)(experimentWithBuckets, userId, segmentationData);
|
|
155
167
|
if (!assignment)
|
|
156
|
-
return
|
|
168
|
+
return {
|
|
169
|
+
experiment,
|
|
170
|
+
assignment: null,
|
|
171
|
+
};
|
|
157
172
|
const newAssignment = await this.userAssignmentDao.save(assignment);
|
|
158
173
|
const bucket = buckets.find(b => b.id === newAssignment.bucketId);
|
|
159
174
|
return {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
175
|
+
experiment,
|
|
176
|
+
assignment: {
|
|
177
|
+
...newAssignment,
|
|
178
|
+
experimentKey: experiment.key,
|
|
179
|
+
bucketKey: bucket?.key || null,
|
|
180
|
+
bucketData: bucket?.data || null,
|
|
181
|
+
},
|
|
164
182
|
};
|
|
165
183
|
}
|
|
166
184
|
/**
|
|
@@ -175,10 +193,13 @@ class Abba {
|
|
|
175
193
|
const experiment = await this.experimentDao.requireById(assignment.experimentId);
|
|
176
194
|
const bucket = await this.bucketDao.getById(assignment.bucketId);
|
|
177
195
|
return {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
196
|
+
experiment,
|
|
197
|
+
assignment: {
|
|
198
|
+
...assignment,
|
|
199
|
+
experimentKey: experiment.key,
|
|
200
|
+
bucketKey: bucket?.key || null,
|
|
201
|
+
bucketData: bucket?.data || null,
|
|
202
|
+
},
|
|
182
203
|
};
|
|
183
204
|
});
|
|
184
205
|
}
|
|
@@ -202,10 +223,13 @@ class Abba {
|
|
|
202
223
|
if (existing) {
|
|
203
224
|
const bucket = experiment.buckets.find(b => b.id === existing.bucketId);
|
|
204
225
|
assignments.push({
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
226
|
+
experiment,
|
|
227
|
+
assignment: {
|
|
228
|
+
...existing,
|
|
229
|
+
experimentKey: experiment.key,
|
|
230
|
+
bucketKey: bucket?.key || null,
|
|
231
|
+
bucketData: bucket?.data || null,
|
|
232
|
+
},
|
|
209
233
|
});
|
|
210
234
|
}
|
|
211
235
|
else if (!existingOnly && (0, util_1.canGenerateNewAssignments)(experiment, exclusionSet)) {
|
|
@@ -215,10 +239,13 @@ class Abba {
|
|
|
215
239
|
newAssignments.push(created);
|
|
216
240
|
const bucket = experiment.buckets.find(b => b.id === created.bucketId);
|
|
217
241
|
assignments.push({
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
242
|
+
experiment,
|
|
243
|
+
assignment: {
|
|
244
|
+
...created,
|
|
245
|
+
experimentKey: experiment.key,
|
|
246
|
+
bucketKey: bucket?.key || null,
|
|
247
|
+
bucketData: bucket?.data || null,
|
|
248
|
+
},
|
|
222
249
|
});
|
|
223
250
|
// Prevent future exclusion clashes
|
|
224
251
|
experiment.exclusions.forEach(experimentId => exclusionSet.add(experimentId));
|
|
@@ -4,6 +4,7 @@ import { BaseExperiment, Experiment } from '../types';
|
|
|
4
4
|
type ExperimentDBM = Saved<BaseExperiment> & {
|
|
5
5
|
rules: string | null;
|
|
6
6
|
exclusions: string | null;
|
|
7
|
+
data: string | null;
|
|
7
8
|
};
|
|
8
9
|
export declare class ExperimentDao extends CommonDao<Experiment, ExperimentDBM> {
|
|
9
10
|
}
|
|
@@ -18,6 +18,7 @@ const experimentDao = (db) => new ExperimentDao({
|
|
|
18
18
|
exclusions: bm.exclusions.length
|
|
19
19
|
? JSON.stringify(bm.exclusions.map(exclusion => exclusion.toString()))
|
|
20
20
|
: null,
|
|
21
|
+
data: bm.data ? JSON.stringify(bm.data) : null,
|
|
21
22
|
}),
|
|
22
23
|
beforeDBMToBM: dbm => ({
|
|
23
24
|
...dbm,
|
|
@@ -29,6 +30,7 @@ const experimentDao = (db) => new ExperimentDao({
|
|
|
29
30
|
exclusions: (dbm.exclusions &&
|
|
30
31
|
JSON.parse(dbm.exclusions).map((exclusion) => exclusion.toString())) ||
|
|
31
32
|
[],
|
|
33
|
+
data: dbm.data ? JSON.parse(dbm.data) : null,
|
|
32
34
|
}),
|
|
33
35
|
},
|
|
34
36
|
});
|
package/dist/migrations/init.sql
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export type BaseExperiment = BaseDBEntity & {
|
|
|
33
33
|
export type Experiment = BaseExperiment & {
|
|
34
34
|
rules: SegmentationRule[];
|
|
35
35
|
exclusions: string[];
|
|
36
|
+
data: AnyObject | null;
|
|
36
37
|
};
|
|
37
38
|
export type ExperimentWithBuckets = Saved<Experiment> & {
|
|
38
39
|
buckets: Saved<Bucket>[];
|
|
@@ -50,11 +51,14 @@ export type UserAssignment = BaseDBEntity & {
|
|
|
50
51
|
experimentId: string;
|
|
51
52
|
bucketId: string | null;
|
|
52
53
|
};
|
|
53
|
-
export
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
export interface GeneratedUserAssignment {
|
|
55
|
+
assignment: (Saved<UserAssignment> & {
|
|
56
|
+
experimentKey: string;
|
|
57
|
+
bucketKey: string | null;
|
|
58
|
+
bucketData: AnyObject | null;
|
|
59
|
+
}) | null;
|
|
60
|
+
experiment: Saved<Experiment>;
|
|
61
|
+
}
|
|
58
62
|
export type SegmentationData = AnyObject;
|
|
59
63
|
export declare enum AssignmentStatus {
|
|
60
64
|
/**
|
package/package.json
CHANGED
package/src/abba.ts
CHANGED
|
@@ -178,12 +178,16 @@ export class Abba {
|
|
|
178
178
|
userId: string,
|
|
179
179
|
existingOnly: boolean,
|
|
180
180
|
segmentationData?: SegmentationData,
|
|
181
|
-
): Promise<GeneratedUserAssignment
|
|
181
|
+
): Promise<GeneratedUserAssignment> {
|
|
182
182
|
const experiment = await this.experimentDao.getOneBy('key', experimentKey)
|
|
183
183
|
_assert(experiment, `Experiment does not exist: ${experimentKey}`)
|
|
184
184
|
|
|
185
185
|
// Inactive experiments should never return an assignment
|
|
186
|
-
if (experiment.status === AssignmentStatus.Inactive)
|
|
186
|
+
if (experiment.status === AssignmentStatus.Inactive)
|
|
187
|
+
return {
|
|
188
|
+
experiment,
|
|
189
|
+
assignment: null,
|
|
190
|
+
}
|
|
187
191
|
|
|
188
192
|
const buckets = await this.bucketDao.getBy('experimentId', experiment.id)
|
|
189
193
|
const existingAssignments = await this.userAssignmentDao.getBy('userId', userId)
|
|
@@ -191,35 +195,53 @@ export class Abba {
|
|
|
191
195
|
if (existing) {
|
|
192
196
|
const bucket = buckets.find(b => b.id === existing.bucketId)
|
|
193
197
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
experiment,
|
|
199
|
+
assignment: {
|
|
200
|
+
...existing,
|
|
201
|
+
experimentKey: experiment.key,
|
|
202
|
+
bucketKey: bucket?.key || null,
|
|
203
|
+
bucketData: bucket?.data || null,
|
|
204
|
+
},
|
|
198
205
|
}
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
// No existing assignment, but we don't want to generate a new one
|
|
202
|
-
if (existingOnly || experiment.status === AssignmentStatus.Paused)
|
|
209
|
+
if (existingOnly || experiment.status === AssignmentStatus.Paused)
|
|
210
|
+
return {
|
|
211
|
+
experiment,
|
|
212
|
+
assignment: null,
|
|
213
|
+
}
|
|
203
214
|
|
|
204
215
|
const experiments = await this.getAllExperiments()
|
|
205
216
|
const exclusionSet = getUserExclusionSet(experiments, existingAssignments)
|
|
206
|
-
if (!canGenerateNewAssignments(experiment, exclusionSet))
|
|
217
|
+
if (!canGenerateNewAssignments(experiment, exclusionSet))
|
|
218
|
+
return {
|
|
219
|
+
experiment,
|
|
220
|
+
assignment: null,
|
|
221
|
+
}
|
|
207
222
|
|
|
208
223
|
_assert(segmentationData, 'Segmentation data required when creating a new assignment')
|
|
209
224
|
|
|
210
225
|
const experimentWithBuckets = { ...experiment, buckets }
|
|
211
226
|
const assignment = generateUserAssignmentData(experimentWithBuckets, userId, segmentationData)
|
|
212
|
-
if (!assignment)
|
|
227
|
+
if (!assignment)
|
|
228
|
+
return {
|
|
229
|
+
experiment,
|
|
230
|
+
assignment: null,
|
|
231
|
+
}
|
|
213
232
|
|
|
214
233
|
const newAssignment = await this.userAssignmentDao.save(assignment)
|
|
215
234
|
|
|
216
235
|
const bucket = buckets.find(b => b.id === newAssignment.bucketId)
|
|
217
236
|
|
|
218
237
|
return {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
238
|
+
experiment,
|
|
239
|
+
assignment: {
|
|
240
|
+
...newAssignment,
|
|
241
|
+
experimentKey: experiment.key,
|
|
242
|
+
bucketKey: bucket?.key || null,
|
|
243
|
+
bucketData: bucket?.data || null,
|
|
244
|
+
},
|
|
223
245
|
}
|
|
224
246
|
}
|
|
225
247
|
|
|
@@ -235,10 +257,13 @@ export class Abba {
|
|
|
235
257
|
const experiment = await this.experimentDao.requireById(assignment.experimentId)
|
|
236
258
|
const bucket = await this.bucketDao.getById(assignment.bucketId)
|
|
237
259
|
return {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
260
|
+
experiment,
|
|
261
|
+
assignment: {
|
|
262
|
+
...assignment,
|
|
263
|
+
experimentKey: experiment.key,
|
|
264
|
+
bucketKey: bucket?.key || null,
|
|
265
|
+
bucketData: bucket?.data || null,
|
|
266
|
+
},
|
|
242
267
|
}
|
|
243
268
|
})
|
|
244
269
|
}
|
|
@@ -273,10 +298,13 @@ export class Abba {
|
|
|
273
298
|
if (existing) {
|
|
274
299
|
const bucket = experiment.buckets.find(b => b.id === existing.bucketId)
|
|
275
300
|
assignments.push({
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
301
|
+
experiment,
|
|
302
|
+
assignment: {
|
|
303
|
+
...existing,
|
|
304
|
+
experimentKey: experiment.key,
|
|
305
|
+
bucketKey: bucket?.key || null,
|
|
306
|
+
bucketData: bucket?.data || null,
|
|
307
|
+
},
|
|
280
308
|
})
|
|
281
309
|
} else if (!existingOnly && canGenerateNewAssignments(experiment, exclusionSet)) {
|
|
282
310
|
const assignment = generateUserAssignmentData(experiment, userId, segmentationData)
|
|
@@ -285,10 +313,13 @@ export class Abba {
|
|
|
285
313
|
newAssignments.push(created)
|
|
286
314
|
const bucket = experiment.buckets.find(b => b.id === created.bucketId)
|
|
287
315
|
assignments.push({
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
316
|
+
experiment,
|
|
317
|
+
assignment: {
|
|
318
|
+
...created,
|
|
319
|
+
experimentKey: experiment.key,
|
|
320
|
+
bucketKey: bucket?.key || null,
|
|
321
|
+
bucketData: bucket?.data || null,
|
|
322
|
+
},
|
|
292
323
|
})
|
|
293
324
|
// Prevent future exclusion clashes
|
|
294
325
|
experiment.exclusions.forEach(experimentId => exclusionSet.add(experimentId))
|
|
@@ -5,6 +5,7 @@ import { BaseExperiment, Experiment } from '../types'
|
|
|
5
5
|
type ExperimentDBM = Saved<BaseExperiment> & {
|
|
6
6
|
rules: string | null
|
|
7
7
|
exclusions: string | null
|
|
8
|
+
data: string | null
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export class ExperimentDao extends CommonDao<Experiment, ExperimentDBM> {}
|
|
@@ -22,6 +23,7 @@ export const experimentDao = (db: CommonDB): ExperimentDao =>
|
|
|
22
23
|
exclusions: bm.exclusions.length
|
|
23
24
|
? JSON.stringify(bm.exclusions.map(exclusion => exclusion.toString()))
|
|
24
25
|
: null,
|
|
26
|
+
data: bm.data ? JSON.stringify(bm.data) : null,
|
|
25
27
|
}),
|
|
26
28
|
beforeDBMToBM: dbm => ({
|
|
27
29
|
...dbm,
|
|
@@ -34,6 +36,7 @@ export const experimentDao = (db: CommonDB): ExperimentDao =>
|
|
|
34
36
|
(dbm.exclusions &&
|
|
35
37
|
JSON.parse(dbm.exclusions).map((exclusion: string | number) => exclusion.toString())) ||
|
|
36
38
|
[],
|
|
39
|
+
data: dbm.data ? JSON.parse(dbm.data) : null,
|
|
37
40
|
}),
|
|
38
41
|
},
|
|
39
42
|
})
|
package/src/migrations/init.sql
CHANGED
package/src/types.ts
CHANGED
|
@@ -36,6 +36,7 @@ export type BaseExperiment = BaseDBEntity & {
|
|
|
36
36
|
export type Experiment = BaseExperiment & {
|
|
37
37
|
rules: SegmentationRule[]
|
|
38
38
|
exclusions: string[]
|
|
39
|
+
data: AnyObject | null
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export type ExperimentWithBuckets = Saved<Experiment> & {
|
|
@@ -58,10 +59,15 @@ export type UserAssignment = BaseDBEntity & {
|
|
|
58
59
|
bucketId: string | null
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
export
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
export interface GeneratedUserAssignment {
|
|
63
|
+
assignment:
|
|
64
|
+
| (Saved<UserAssignment> & {
|
|
65
|
+
experimentKey: string
|
|
66
|
+
bucketKey: string | null
|
|
67
|
+
bucketData: AnyObject | null
|
|
68
|
+
})
|
|
69
|
+
| null
|
|
70
|
+
experiment: Saved<Experiment>
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
export type SegmentationData = AnyObject
|