@naturalcycles/abba 1.7.0 → 1.9.1
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 +45 -72
- package/dist/abba.js +119 -165
- package/dist/dao/bucket.dao.d.ts +5 -0
- package/dist/dao/bucket.dao.js +15 -0
- package/dist/dao/experiment.dao.d.ts +10 -0
- package/dist/dao/experiment.dao.js +19 -0
- package/dist/dao/userAssignment.dao.d.ts +5 -0
- package/dist/dao/userAssignment.dao.js +15 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/migrations/init.sql +47 -0
- package/dist/types.d.ts +30 -7
- package/dist/util.d.ts +5 -21
- package/dist/util.js +0 -16
- package/package.json +9 -9
- package/readme.md +14 -15
- package/src/abba.ts +160 -191
- package/src/dao/bucket.dao.ts +13 -0
- package/src/dao/experiment.dao.ts +22 -0
- package/src/dao/userAssignment.dao.ts +13 -0
- package/src/index.ts +0 -3
- package/src/migrations/init.sql +47 -0
- package/src/types.ts +41 -7
- package/src/util.ts +5 -21
- package/dist/prisma-output/index-browser.js +0 -141
- package/dist/prisma-output/index.d.ts +0 -5526
- package/dist/prisma-output/index.js +0 -217
- package/dist/prisma-output/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/dist/prisma-output/libquery_engine-darwin.dylib.node +0 -0
- package/dist/prisma-output/libquery_engine-debian-openssl-1.1.x.so.node +0 -0
- package/dist/prisma-output/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/dist/prisma-output/runtime/esm/index-browser.mjs +0 -2370
- package/dist/prisma-output/runtime/esm/index.mjs +0 -40587
- package/dist/prisma-output/runtime/esm/proxy.mjs +0 -113
- package/dist/prisma-output/runtime/index-browser.d.ts +0 -269
- package/dist/prisma-output/runtime/index-browser.js +0 -2621
- package/dist/prisma-output/runtime/index.d.ts +0 -1384
- package/dist/prisma-output/runtime/index.js +0 -59183
- package/dist/prisma-output/runtime/proxy.d.ts +0 -1384
- package/dist/prisma-output/runtime/proxy.js +0 -13576
- package/dist/prisma-output/schema.prisma +0 -47
- package/src/prisma-output/index-browser.js +0 -141
- package/src/prisma-output/index.d.ts +0 -5526
- package/src/prisma-output/index.js +0 -217
- package/src/prisma-output/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/src/prisma-output/libquery_engine-darwin.dylib.node +0 -0
- package/src/prisma-output/libquery_engine-debian-openssl-1.1.x.so.node +0 -0
- package/src/prisma-output/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/src/prisma-output/runtime/esm/index-browser.mjs +0 -2370
- package/src/prisma-output/runtime/esm/index.mjs +0 -40587
- package/src/prisma-output/runtime/esm/proxy.mjs +0 -113
- package/src/prisma-output/runtime/index-browser.d.ts +0 -269
- package/src/prisma-output/runtime/index-browser.js +0 -2621
- package/src/prisma-output/runtime/index.d.ts +0 -1384
- package/src/prisma-output/runtime/index.js +0 -59183
- package/src/prisma-output/runtime/proxy.d.ts +0 -1384
- package/src/prisma-output/runtime/proxy.js +0 -13576
- package/src/prisma-output/schema.prisma +0 -47
package/dist/abba.d.ts
CHANGED
|
@@ -1,102 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Saved } from '@naturalcycles/js-lib';
|
|
2
|
+
import { AbbaConfig, Bucket, BucketInput, Experiment, ExperimentWithBuckets, UserAssignment } from './types';
|
|
3
|
+
import { SegmentationData, AssignmentStatistics } from '.';
|
|
3
4
|
export declare class Abba {
|
|
4
|
-
|
|
5
|
-
constructor(
|
|
5
|
+
cfg: AbbaConfig;
|
|
6
|
+
constructor(cfg: AbbaConfig);
|
|
7
|
+
private experimentDao;
|
|
8
|
+
private bucketDao;
|
|
9
|
+
private userAssignmentDao;
|
|
6
10
|
/**
|
|
7
|
-
* Returns all experiments
|
|
11
|
+
* Returns all (active and inactive) experiments.
|
|
8
12
|
*
|
|
9
|
-
*
|
|
13
|
+
* Cold method, not cached.
|
|
10
14
|
*/
|
|
11
|
-
getAllExperiments(
|
|
15
|
+
getAllExperiments(): Promise<ExperimentWithBuckets[]>;
|
|
12
16
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @param buckets
|
|
17
|
-
* @returns
|
|
17
|
+
* Returns only active experiments.
|
|
18
|
+
* Hot method.
|
|
19
|
+
* Cached in-memory for N minutes (currently 10).
|
|
18
20
|
*/
|
|
19
|
-
|
|
21
|
+
getActiveExperiments(): Promise<ExperimentWithBuckets[]>;
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @param id
|
|
24
|
-
* @param experiment
|
|
25
|
-
* @param rules
|
|
26
|
-
* @param buckets
|
|
27
|
-
* @returns
|
|
23
|
+
* Creates a new experiment.
|
|
24
|
+
* Cold method.
|
|
28
25
|
*/
|
|
29
|
-
|
|
26
|
+
createExperiment(experiment: Experiment, buckets: BucketInput[]): Promise<ExperimentWithBuckets>;
|
|
30
27
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
28
|
+
* Update experiment information, will also validate the buckets' ratio if experiment.active is true
|
|
29
|
+
* Cold method.
|
|
30
|
+
*/
|
|
31
|
+
saveExperiment(experiment: Experiment & {
|
|
32
|
+
id: number;
|
|
33
|
+
}, buckets: Bucket[]): Promise<ExperimentWithBuckets>;
|
|
34
|
+
/**
|
|
35
|
+
* Delete an experiment. Removes all user assignments and buckets.
|
|
36
|
+
* Cold method.
|
|
34
37
|
*/
|
|
35
38
|
deleteExperiment(id: number): Promise<void>;
|
|
36
39
|
/**
|
|
37
|
-
* Get an assignment for a given user. If existingOnly is false, it will attempt generate a new assignment
|
|
40
|
+
* Get an assignment for a given user. If existingOnly is false, it will attempt to generate a new assignment
|
|
41
|
+
* Cold method.
|
|
38
42
|
*
|
|
39
43
|
* @param experimentId
|
|
40
44
|
* @param userId
|
|
41
|
-
* @param
|
|
42
|
-
* @param segmentationData
|
|
43
|
-
* @returns
|
|
45
|
+
* @param existingOnly Do not generate any new assignments for this experiment
|
|
46
|
+
* @param segmentationData Required if existingOnly is false
|
|
44
47
|
*/
|
|
45
|
-
getUserAssignment(experimentId: number, userId: string, existingOnly: boolean, segmentationData?: SegmentationData): Promise<UserAssignment | null>;
|
|
48
|
+
getUserAssignment(experimentId: number, userId: string, existingOnly: boolean, segmentationData?: SegmentationData): Promise<Saved<UserAssignment> | null>;
|
|
46
49
|
/**
|
|
47
|
-
* Get all existing user assignments
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* @returns
|
|
50
|
+
* Get all existing user assignments.
|
|
51
|
+
* Hot method.
|
|
52
|
+
* Not cached, because Assignments are fast-changing.
|
|
51
53
|
*/
|
|
52
|
-
getAllExistingUserAssignments(userId: string): Promise<UserAssignment[]>;
|
|
54
|
+
getAllExistingUserAssignments(userId: string): Promise<Saved<UserAssignment>[]>;
|
|
53
55
|
/**
|
|
54
|
-
* Generate user assignments for all active experiments.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* @param segmentationData
|
|
58
|
-
* @returns
|
|
56
|
+
* Generate user assignments for all active experiments.
|
|
57
|
+
* Will return any existing and attempt to generate any new assignments.
|
|
58
|
+
* Hot method.
|
|
59
59
|
*/
|
|
60
|
-
generateUserAssignments(userId: string, segmentationData: SegmentationData): Promise<UserAssignment[]>;
|
|
60
|
+
generateUserAssignments(userId: string, segmentationData: SegmentationData): Promise<Saved<UserAssignment>[]>;
|
|
61
61
|
/**
|
|
62
|
-
* Get assignment statistics for an experiment
|
|
63
|
-
*
|
|
64
|
-
* @param experimentId
|
|
65
|
-
* @returns
|
|
62
|
+
* Get assignment statistics for an experiment.
|
|
63
|
+
* Cold method.
|
|
66
64
|
*/
|
|
67
65
|
getExperimentAssignmentStatistics(experimentId: number): Promise<AssignmentStatistics>;
|
|
68
66
|
/**
|
|
69
|
-
* Generate a new assignment for a given user
|
|
70
|
-
*
|
|
71
|
-
* @param experimentId
|
|
72
|
-
* @param userId
|
|
73
|
-
* @param segmentationData
|
|
74
|
-
* @returns
|
|
67
|
+
* Generate a new assignment for a given user.
|
|
68
|
+
* Doesn't save it.
|
|
75
69
|
*/
|
|
76
|
-
private
|
|
70
|
+
private generateUserAssignmentData;
|
|
77
71
|
/**
|
|
78
72
|
* Queries to retrieve an existing user assignment for a given experiment
|
|
79
|
-
*
|
|
80
|
-
* @param experimentId
|
|
81
|
-
* @param userId
|
|
82
|
-
* @returns
|
|
83
73
|
*/
|
|
84
74
|
private getExistingUserAssignment;
|
|
85
|
-
/**
|
|
86
|
-
* Update experiment information
|
|
87
|
-
*
|
|
88
|
-
* @param id
|
|
89
|
-
* @param experiment
|
|
90
|
-
* @param rules
|
|
91
|
-
* @returns
|
|
92
|
-
*/
|
|
93
|
-
private updateExperiment;
|
|
94
|
-
/**
|
|
95
|
-
* Upserts bucket info
|
|
96
|
-
*
|
|
97
|
-
* @param id
|
|
98
|
-
* @param experiment
|
|
99
|
-
* @returns
|
|
100
|
-
*/
|
|
101
|
-
private saveBuckets;
|
|
102
75
|
}
|
package/dist/abba.js
CHANGED
|
@@ -1,245 +1,199 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Abba = void 0;
|
|
4
|
-
const
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
+
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
5
7
|
const types_1 = require("./types");
|
|
6
8
|
const util_1 = require("./util");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
const experiment_dao_1 = require("./dao/experiment.dao");
|
|
10
|
+
const userAssignment_dao_1 = require("./dao/userAssignment.dao");
|
|
11
|
+
const bucket_dao_1 = require("./dao/bucket.dao");
|
|
12
|
+
const CACHE_TTL = 600000; // 10 minutes
|
|
10
13
|
class Abba {
|
|
11
|
-
constructor(
|
|
12
|
-
this.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
});
|
|
14
|
+
constructor(cfg) {
|
|
15
|
+
this.cfg = cfg;
|
|
16
|
+
const { db } = cfg;
|
|
17
|
+
this.experimentDao = (0, experiment_dao_1.experimentDao)(db);
|
|
18
|
+
this.bucketDao = (0, bucket_dao_1.bucketDao)(db);
|
|
19
|
+
this.userAssignmentDao = (0, userAssignment_dao_1.userAssignmentDao)(db);
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
|
-
* Returns all experiments
|
|
22
|
+
* Returns all (active and inactive) experiments.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
+
* Cold method, not cached.
|
|
24
25
|
*/
|
|
25
|
-
async getAllExperiments(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
async getAllExperiments() {
|
|
27
|
+
const experiments = await this.experimentDao.query().runQuery();
|
|
28
|
+
const buckets = await this.bucketDao
|
|
29
|
+
.query()
|
|
30
|
+
.filter('experimentId', 'in', experiments.map(e => e.id))
|
|
31
|
+
.runQuery();
|
|
32
|
+
return experiments.map(experiment => ({
|
|
33
|
+
...experiment,
|
|
34
|
+
buckets: buckets.filter(bucket => bucket.experimentId === experiment.id),
|
|
35
|
+
}));
|
|
30
36
|
}
|
|
31
37
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
* Returns only active experiments.
|
|
39
|
+
* Hot method.
|
|
40
|
+
* Cached in-memory for N minutes (currently 10).
|
|
41
|
+
*/
|
|
42
|
+
async getActiveExperiments() {
|
|
43
|
+
const experiments = await this.experimentDao
|
|
44
|
+
.query()
|
|
45
|
+
.filter('status', '!=', types_1.AssignmentStatus.Inactive)
|
|
46
|
+
.runQuery();
|
|
47
|
+
const buckets = await this.bucketDao
|
|
48
|
+
.query()
|
|
49
|
+
.filter('experimentId', 'in', experiments.map(e => e.id))
|
|
50
|
+
.runQuery();
|
|
51
|
+
return experiments.map(experiment => ({
|
|
52
|
+
...experiment,
|
|
53
|
+
buckets: buckets.filter(bucket => bucket.experimentId === experiment.id),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a new experiment.
|
|
58
|
+
* Cold method.
|
|
37
59
|
*/
|
|
38
60
|
async createExperiment(experiment, buckets) {
|
|
39
61
|
if (experiment.status === types_1.AssignmentStatus.Active) {
|
|
40
62
|
(0, util_1.validateTotalBucketRatio)(buckets);
|
|
41
63
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
createMany: {
|
|
48
|
-
data: buckets,
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
include: {
|
|
53
|
-
buckets: true,
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
return created;
|
|
64
|
+
await this.experimentDao.save(experiment);
|
|
65
|
+
return {
|
|
66
|
+
...experiment,
|
|
67
|
+
buckets: await this.bucketDao.saveBatch(buckets.map(b => ({ ...b, experimentId: experiment.id }))),
|
|
68
|
+
};
|
|
57
69
|
}
|
|
58
70
|
/**
|
|
59
|
-
* Update experiment information, will also validate the buckets ratio if experiment.active is true
|
|
60
|
-
*
|
|
61
|
-
* @param id
|
|
62
|
-
* @param experiment
|
|
63
|
-
* @param rules
|
|
64
|
-
* @param buckets
|
|
65
|
-
* @returns
|
|
71
|
+
* Update experiment information, will also validate the buckets' ratio if experiment.active is true
|
|
72
|
+
* Cold method.
|
|
66
73
|
*/
|
|
67
|
-
async saveExperiment(
|
|
74
|
+
async saveExperiment(experiment, buckets) {
|
|
68
75
|
if (experiment.status === types_1.AssignmentStatus.Active) {
|
|
69
76
|
(0, util_1.validateTotalBucketRatio)(buckets);
|
|
70
77
|
}
|
|
71
|
-
|
|
72
|
-
const updatedBuckets = await this.saveBuckets(buckets);
|
|
78
|
+
await this.experimentDao.save(experiment);
|
|
73
79
|
return {
|
|
74
|
-
...
|
|
75
|
-
buckets:
|
|
80
|
+
...experiment,
|
|
81
|
+
buckets: await this.bucketDao.saveBatch(buckets.map(b => ({ ...b, experimentId: experiment.id }))),
|
|
76
82
|
};
|
|
77
83
|
}
|
|
78
84
|
/**
|
|
79
|
-
* Delete an experiment. Removes all user assignments and buckets
|
|
80
|
-
*
|
|
81
|
-
* @param id
|
|
85
|
+
* Delete an experiment. Removes all user assignments and buckets.
|
|
86
|
+
* Cold method.
|
|
82
87
|
*/
|
|
83
88
|
async deleteExperiment(id) {
|
|
84
|
-
await this.
|
|
89
|
+
await this.experimentDao.deleteById(id);
|
|
85
90
|
}
|
|
86
91
|
/**
|
|
87
|
-
* Get an assignment for a given user. If existingOnly is false, it will attempt generate a new assignment
|
|
92
|
+
* Get an assignment for a given user. If existingOnly is false, it will attempt to generate a new assignment
|
|
93
|
+
* Cold method.
|
|
88
94
|
*
|
|
89
95
|
* @param experimentId
|
|
90
96
|
* @param userId
|
|
91
|
-
* @param
|
|
92
|
-
* @param segmentationData
|
|
93
|
-
* @returns
|
|
97
|
+
* @param existingOnly Do not generate any new assignments for this experiment
|
|
98
|
+
* @param segmentationData Required if existingOnly is false
|
|
94
99
|
*/
|
|
95
100
|
async getUserAssignment(experimentId, userId, existingOnly, segmentationData) {
|
|
96
|
-
const experiment = await this.client.experiment.findUnique({
|
|
97
|
-
where: { id: experimentId },
|
|
98
|
-
include: { buckets: true },
|
|
99
|
-
});
|
|
100
|
-
if (!experiment)
|
|
101
|
-
throw new Error('Experiment not found');
|
|
102
101
|
const existing = await this.getExistingUserAssignment(experimentId, userId);
|
|
103
102
|
if (existing)
|
|
104
103
|
return existing;
|
|
105
|
-
if (
|
|
104
|
+
if (existingOnly)
|
|
105
|
+
return null;
|
|
106
|
+
const experiment = await this.experimentDao.requireById(experimentId);
|
|
107
|
+
if (experiment.status !== types_1.AssignmentStatus.Active)
|
|
108
|
+
return null;
|
|
109
|
+
(0, js_lib_1._assert)(segmentationData, 'Segmentation data required when creating a new assignment');
|
|
110
|
+
const buckets = await this.bucketDao.getBy('experimentId', experimentId);
|
|
111
|
+
const assignment = this.generateUserAssignmentData({ ...experiment, buckets }, userId, segmentationData);
|
|
112
|
+
if (!assignment)
|
|
106
113
|
return null;
|
|
107
|
-
|
|
108
|
-
throw new Error('Segmentation data required when creating a new assignment');
|
|
109
|
-
return await this.generateUserAssignment(experiment, userId, segmentationData);
|
|
114
|
+
return await this.userAssignmentDao.save(assignment);
|
|
110
115
|
}
|
|
111
116
|
/**
|
|
112
|
-
* Get all existing user assignments
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @returns
|
|
117
|
+
* Get all existing user assignments.
|
|
118
|
+
* Hot method.
|
|
119
|
+
* Not cached, because Assignments are fast-changing.
|
|
116
120
|
*/
|
|
117
121
|
async getAllExistingUserAssignments(userId) {
|
|
118
|
-
return await this.
|
|
122
|
+
return await this.userAssignmentDao.getBy('userId', userId);
|
|
119
123
|
}
|
|
120
124
|
/**
|
|
121
|
-
* Generate user assignments for all active experiments.
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* @param segmentationData
|
|
125
|
-
* @returns
|
|
125
|
+
* Generate user assignments for all active experiments.
|
|
126
|
+
* Will return any existing and attempt to generate any new assignments.
|
|
127
|
+
* Hot method.
|
|
126
128
|
*/
|
|
127
129
|
async generateUserAssignments(userId, segmentationData) {
|
|
128
|
-
const experiments = await this.
|
|
130
|
+
const experiments = await this.getActiveExperiments(); // cached
|
|
129
131
|
const existingAssignments = await this.getAllExistingUserAssignments(userId);
|
|
130
|
-
const
|
|
132
|
+
const allAssignments = [];
|
|
131
133
|
const generatedAssignments = [];
|
|
132
134
|
for (const experiment of experiments) {
|
|
133
135
|
const existing = existingAssignments.find(ua => ua.experimentId === experiment.id);
|
|
134
136
|
if (existing) {
|
|
135
|
-
|
|
136
|
-
continue;
|
|
137
|
+
allAssignments.push(existing);
|
|
137
138
|
}
|
|
138
139
|
else {
|
|
139
|
-
|
|
140
|
+
const assignment = this.generateUserAssignmentData(experiment, userId, segmentationData);
|
|
141
|
+
if (assignment) {
|
|
142
|
+
generatedAssignments.push(assignment);
|
|
143
|
+
}
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return [...assignments, ...filtered];
|
|
146
|
+
await this.userAssignmentDao.saveBatch(generatedAssignments);
|
|
147
|
+
return [...allAssignments, ...generatedAssignments];
|
|
145
148
|
}
|
|
146
149
|
/**
|
|
147
|
-
* Get assignment statistics for an experiment
|
|
148
|
-
*
|
|
149
|
-
* @param experimentId
|
|
150
|
-
* @returns
|
|
150
|
+
* Get assignment statistics for an experiment.
|
|
151
|
+
* Cold method.
|
|
151
152
|
*/
|
|
152
153
|
async getExperimentAssignmentStatistics(experimentId) {
|
|
153
154
|
const statistics = {
|
|
154
|
-
sampled: await this.
|
|
155
|
+
sampled: await this.userAssignmentDao
|
|
156
|
+
.query()
|
|
157
|
+
.filterEq('experimentId', experimentId)
|
|
158
|
+
.runQueryCount(),
|
|
155
159
|
buckets: {},
|
|
156
160
|
};
|
|
157
|
-
const buckets = await this.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
buckets.forEach(({ id }) => {
|
|
166
|
-
statistics.buckets[`${id}`] = assignmentCounts.find(i => i.bucketId === id)?._count?._all || 0;
|
|
161
|
+
const buckets = await this.bucketDao.getBy('experimentId', experimentId);
|
|
162
|
+
await (0, js_lib_1.pMap)(buckets, async (bucket) => {
|
|
163
|
+
statistics[bucket.id] = await this.userAssignmentDao
|
|
164
|
+
.query()
|
|
165
|
+
.filterEq('bucketId', bucket.id)
|
|
166
|
+
.runQueryCount();
|
|
167
167
|
});
|
|
168
168
|
return statistics;
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
|
-
* Generate a new assignment for a given user
|
|
172
|
-
*
|
|
173
|
-
* @param experimentId
|
|
174
|
-
* @param userId
|
|
175
|
-
* @param segmentationData
|
|
176
|
-
* @returns
|
|
171
|
+
* Generate a new assignment for a given user.
|
|
172
|
+
* Doesn't save it.
|
|
177
173
|
*/
|
|
178
|
-
|
|
174
|
+
generateUserAssignmentData(experiment, userId, segmentationData) {
|
|
179
175
|
const segmentationMatch = (0, util_1.validateSegmentationRules)(experiment.rules, segmentationData);
|
|
180
176
|
if (!segmentationMatch)
|
|
181
177
|
return null;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
178
|
+
return {
|
|
179
|
+
userId,
|
|
180
|
+
experimentId: experiment.id,
|
|
181
|
+
bucketId: (0, util_1.determineAssignment)(experiment.sampling, experiment.buckets),
|
|
182
|
+
};
|
|
186
183
|
}
|
|
187
184
|
/**
|
|
188
185
|
* Queries to retrieve an existing user assignment for a given experiment
|
|
189
|
-
*
|
|
190
|
-
* @param experimentId
|
|
191
|
-
* @param userId
|
|
192
|
-
* @returns
|
|
193
186
|
*/
|
|
194
187
|
async getExistingUserAssignment(experimentId, userId) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* @param experiment
|
|
202
|
-
* @param rules
|
|
203
|
-
* @returns
|
|
204
|
-
*/
|
|
205
|
-
async updateExperiment(id, experiment) {
|
|
206
|
-
return await this.client.experiment.update({
|
|
207
|
-
where: { id },
|
|
208
|
-
data: {
|
|
209
|
-
...experiment,
|
|
210
|
-
rules: experiment.rules,
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Upserts bucket info
|
|
216
|
-
*
|
|
217
|
-
* @param id
|
|
218
|
-
* @param experiment
|
|
219
|
-
* @returns
|
|
220
|
-
*/
|
|
221
|
-
async saveBuckets(buckets) {
|
|
222
|
-
const savedBuckets = [];
|
|
223
|
-
for (const bucket of buckets) {
|
|
224
|
-
const { id, ...data } = bucket;
|
|
225
|
-
if (id) {
|
|
226
|
-
savedBuckets.push(this.client.bucket.update({ where: { id }, data }));
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
savedBuckets.push(this.client.bucket.create({
|
|
230
|
-
data: {
|
|
231
|
-
key: data.key,
|
|
232
|
-
ratio: data.ratio,
|
|
233
|
-
experiment: {
|
|
234
|
-
connect: {
|
|
235
|
-
id: data.experimentId,
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
}));
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return await Promise.all(savedBuckets);
|
|
188
|
+
const [assignment] = await this.userAssignmentDao
|
|
189
|
+
.query()
|
|
190
|
+
.filterEq('userId', userId)
|
|
191
|
+
.filterEq('experimentId', experimentId)
|
|
192
|
+
.runQuery();
|
|
193
|
+
return assignment || null;
|
|
243
194
|
}
|
|
244
195
|
}
|
|
196
|
+
tslib_1.__decorate([
|
|
197
|
+
(0, js_lib_1._AsyncMemo)({ cacheFactory: () => new nodejs_lib_1.LRUMemoCache({ ttl: CACHE_TTL, max: 1 }) })
|
|
198
|
+
], Abba.prototype, "getActiveExperiments", null);
|
|
245
199
|
exports.Abba = Abba;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bucketDao = exports.BucketDao = void 0;
|
|
4
|
+
const db_lib_1 = require("@naturalcycles/db-lib");
|
|
5
|
+
class BucketDao extends db_lib_1.CommonDao {
|
|
6
|
+
}
|
|
7
|
+
exports.BucketDao = BucketDao;
|
|
8
|
+
const bucketDao = (db) => new BucketDao({
|
|
9
|
+
db,
|
|
10
|
+
table: 'Bucket',
|
|
11
|
+
createId: false,
|
|
12
|
+
idType: 'number',
|
|
13
|
+
assignGeneratedIds: true,
|
|
14
|
+
});
|
|
15
|
+
exports.bucketDao = bucketDao;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CommonDao, CommonDB } from '@naturalcycles/db-lib';
|
|
2
|
+
import { Saved } from '@naturalcycles/js-lib';
|
|
3
|
+
import { BaseExperiment, Experiment } from '../types';
|
|
4
|
+
declare type ExperimentDBM = Saved<BaseExperiment> & {
|
|
5
|
+
rules: string | null;
|
|
6
|
+
};
|
|
7
|
+
export declare class ExperimentDao extends CommonDao<Experiment, ExperimentDBM> {
|
|
8
|
+
}
|
|
9
|
+
export declare const experimentDao: (db: CommonDB) => ExperimentDao;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.experimentDao = exports.ExperimentDao = void 0;
|
|
4
|
+
const db_lib_1 = require("@naturalcycles/db-lib");
|
|
5
|
+
class ExperimentDao extends db_lib_1.CommonDao {
|
|
6
|
+
}
|
|
7
|
+
exports.ExperimentDao = ExperimentDao;
|
|
8
|
+
const experimentDao = (db) => new ExperimentDao({
|
|
9
|
+
db,
|
|
10
|
+
table: 'Experiment',
|
|
11
|
+
createId: false,
|
|
12
|
+
idType: 'number',
|
|
13
|
+
assignGeneratedIds: true,
|
|
14
|
+
hooks: {
|
|
15
|
+
beforeBMToDBM: bm => ({ ...bm, rules: bm.rules.length ? JSON.stringify(bm.rules) : null }),
|
|
16
|
+
beforeDBMToBM: dbm => ({ ...dbm, rules: (dbm.rules && JSON.parse(dbm.rules)) || [] }),
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
exports.experimentDao = experimentDao;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.userAssignmentDao = exports.UserAssignmentDao = void 0;
|
|
4
|
+
const db_lib_1 = require("@naturalcycles/db-lib");
|
|
5
|
+
class UserAssignmentDao extends db_lib_1.CommonDao {
|
|
6
|
+
}
|
|
7
|
+
exports.UserAssignmentDao = UserAssignmentDao;
|
|
8
|
+
const userAssignmentDao = (db) => new UserAssignmentDao({
|
|
9
|
+
db,
|
|
10
|
+
table: 'UserAssignment',
|
|
11
|
+
createId: false,
|
|
12
|
+
idType: 'number',
|
|
13
|
+
assignGeneratedIds: true,
|
|
14
|
+
});
|
|
15
|
+
exports.userAssignmentDao = userAssignmentDao;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Abba = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
|
|
5
|
+
tslib_1.__exportStar(require("./types"), exports);
|
|
6
6
|
var abba_1 = require("./abba");
|
|
7
7
|
Object.defineProperty(exports, "Abba", { enumerable: true, get: function () { return abba_1.Abba; } });
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE IF NOT EXISTS `Bucket` (
|
|
3
|
+
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
|
4
|
+
`experimentId` INTEGER NOT NULL,
|
|
5
|
+
`key` VARCHAR(10) NOT NULL,
|
|
6
|
+
`ratio` INTEGER NOT NULL,
|
|
7
|
+
`created` INTEGER(11) NOT NULL,
|
|
8
|
+
`updated` INTEGER(11) NOT NULL,
|
|
9
|
+
|
|
10
|
+
PRIMARY KEY (`id`)
|
|
11
|
+
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
12
|
+
|
|
13
|
+
-- CreateTable
|
|
14
|
+
CREATE TABLE IF NOT EXISTS `Experiment` (
|
|
15
|
+
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
|
16
|
+
`name` VARCHAR(191) NOT NULL,
|
|
17
|
+
`status` INTEGER NOT NULL,
|
|
18
|
+
`sampling` INTEGER NOT NULL,
|
|
19
|
+
`description` VARCHAR(240) NULL,
|
|
20
|
+
`created` INTEGER(11) NOT NULL,
|
|
21
|
+
`updated` INTEGER(11) NOT NULL,
|
|
22
|
+
`rules` JSON NULL,
|
|
23
|
+
|
|
24
|
+
PRIMARY KEY (`id`)
|
|
25
|
+
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
26
|
+
|
|
27
|
+
-- CreateTable
|
|
28
|
+
CREATE TABLE IF NOT EXISTS `UserAssignment` (
|
|
29
|
+
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
|
30
|
+
`userId` VARCHAR(191) NOT NULL,
|
|
31
|
+
`experimentId` INTEGER NOT NULL,
|
|
32
|
+
`bucketId` INTEGER NULL,
|
|
33
|
+
`created` INTEGER(11) NOT NULL,
|
|
34
|
+
`updated` INTEGER(11) NOT NULL,
|
|
35
|
+
|
|
36
|
+
UNIQUE INDEX `UserAssignment_userId_experimentId_key`(`userId`, `experimentId`),
|
|
37
|
+
PRIMARY KEY (`id`)
|
|
38
|
+
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
39
|
+
|
|
40
|
+
-- AddForeignKey
|
|
41
|
+
ALTER TABLE `Bucket` ADD CONSTRAINT `Bucket_experimentId_fkey` FOREIGN KEY (`experimentId`) REFERENCES `Experiment`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
42
|
+
|
|
43
|
+
-- AddForeignKey
|
|
44
|
+
ALTER TABLE `UserAssignment` ADD CONSTRAINT `UserAssignment_bucketId_fkey` FOREIGN KEY (`bucketId`) REFERENCES `Bucket`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
45
|
+
|
|
46
|
+
-- AddForeignKey
|
|
47
|
+
ALTER TABLE `UserAssignment` ADD CONSTRAINT `UserAssignment_experimentId_fkey` FOREIGN KEY (`experimentId`) REFERENCES `Experiment`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|