@naturalcycles/abba 2.6.0 → 2.7.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 +3 -6
- package/dist/abba.js +16 -9
- package/dist/dao/experiment.dao.js +1 -1
- package/dist/types.d.ts +22 -1
- package/dist/types.js +6 -3
- package/dist/util.d.ts +2 -2
- package/dist/util.js +10 -0
- package/package.json +4 -3
- package/src/abba.ts +14 -4
- package/src/dao/experiment.dao.ts +1 -1
- package/src/types.ts +23 -1
- package/src/util.ts +10 -1
- package/dist/migrations/init.sql +0 -54
package/dist/abba.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Unsaved } from '@naturalcycles/js-lib/types';
|
|
2
|
-
import {
|
|
3
|
-
import type { AbbaConfig, Bucket, DecoratedUserAssignment, Experiment, ExperimentAssignmentStatistics, ExperimentWithBuckets, SegmentationData, UserExperiment } from './types.js';
|
|
2
|
+
import type { GetAllExperimentsOpts } from './dao/experiment.dao.js';
|
|
3
|
+
import type { AbbaConfig, Bucket, BucketInput, DecoratedUserAssignment, Experiment, ExperimentAssignmentStatistics, ExperimentInput, ExperimentWithBuckets, SegmentationData, UserExperiment } from './types.js';
|
|
4
4
|
export declare class Abba {
|
|
5
5
|
cfg: AbbaConfig;
|
|
6
6
|
private experimentDao;
|
|
@@ -26,15 +26,12 @@ export declare class Abba {
|
|
|
26
26
|
* Creates a new experiment.
|
|
27
27
|
* Cold method.
|
|
28
28
|
*/
|
|
29
|
-
createExperiment(
|
|
29
|
+
createExperiment(input: ExperimentInput, buckets: BucketInput[]): Promise<ExperimentWithBuckets>;
|
|
30
30
|
/**
|
|
31
31
|
* Update experiment information, will also validate the buckets' ratio if experiment.active is true
|
|
32
32
|
* Cold method.
|
|
33
33
|
*/
|
|
34
34
|
saveExperiment(experiment: Experiment, buckets: Unsaved<Bucket>[]): Promise<ExperimentWithBuckets>;
|
|
35
|
-
/**
|
|
36
|
-
* Ensures that mutual exclusions are maintained
|
|
37
|
-
*/
|
|
38
35
|
private updateExclusions;
|
|
39
36
|
softDeleteExperiment(experimentId: string): Promise<void>;
|
|
40
37
|
/**
|
package/dist/abba.js
CHANGED
|
@@ -15,11 +15,12 @@ import { canGenerateNewAssignments, generateUserAssignmentData, getUserExclusion
|
|
|
15
15
|
*/
|
|
16
16
|
const CACHE_TTL = 600_000;
|
|
17
17
|
export class Abba {
|
|
18
|
+
cfg;
|
|
19
|
+
experimentDao = experimentDao(this.cfg.db);
|
|
20
|
+
bucketDao = bucketDao(this.cfg.db);
|
|
21
|
+
userAssignmentDao = userAssignmentDao(this.cfg.db);
|
|
18
22
|
constructor(cfg) {
|
|
19
23
|
this.cfg = cfg;
|
|
20
|
-
this.experimentDao = experimentDao(this.cfg.db);
|
|
21
|
-
this.bucketDao = bucketDao(this.cfg.db);
|
|
22
|
-
this.userAssignmentDao = userAssignmentDao(this.cfg.db);
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Returns all experiments.
|
|
@@ -68,7 +69,7 @@ export class Abba {
|
|
|
68
69
|
async mergeAssignmentsForUserIds(fromUserId, intoUserId) {
|
|
69
70
|
const fromAssignments = await this.userAssignmentDao.getBy('userId', fromUserId);
|
|
70
71
|
const existingIntoAssignments = await this.userAssignmentDao.getBy('userId', intoUserId);
|
|
71
|
-
await pMap(fromAssignments, async
|
|
72
|
+
await pMap(fromAssignments, async from => {
|
|
72
73
|
if (!existingIntoAssignments.some(into => into.experimentId === from.experimentId)) {
|
|
73
74
|
await this.userAssignmentDao.patch(from, { userId: intoUserId });
|
|
74
75
|
}
|
|
@@ -78,10 +79,16 @@ export class Abba {
|
|
|
78
79
|
* Creates a new experiment.
|
|
79
80
|
* Cold method.
|
|
80
81
|
*/
|
|
81
|
-
async createExperiment(
|
|
82
|
-
if (
|
|
82
|
+
async createExperiment(input, buckets) {
|
|
83
|
+
if (input.status === AssignmentStatus.Active) {
|
|
83
84
|
validateTotalBucketRatio(buckets);
|
|
84
85
|
}
|
|
86
|
+
const experiment = {
|
|
87
|
+
...input,
|
|
88
|
+
deleted: false,
|
|
89
|
+
description: input.description ?? null,
|
|
90
|
+
rules: input.rules ?? [],
|
|
91
|
+
};
|
|
85
92
|
const created = await this.experimentDao.save(experiment);
|
|
86
93
|
const createdbuckets = await this.bucketDao.saveBatch(buckets.map(b => ({ ...b, experimentId: created.id })));
|
|
87
94
|
await this.updateExclusions(created.id, created.exclusions);
|
|
@@ -99,7 +106,7 @@ export class Abba {
|
|
|
99
106
|
validateTotalBucketRatio(buckets);
|
|
100
107
|
}
|
|
101
108
|
const updatedExperiment = await this.experimentDao.save(experiment, { saveMethod: 'update' });
|
|
102
|
-
const updatedBuckets = await pMap(buckets, async
|
|
109
|
+
const updatedBuckets = await pMap(buckets, async bucket => {
|
|
103
110
|
return await this.bucketDao.save({ ...bucket, experimentId: updatedExperiment.id }, { saveMethod: bucket.id ? 'update' : undefined });
|
|
104
111
|
});
|
|
105
112
|
await this.updateExclusions(updatedExperiment.id, updatedExperiment.exclusions);
|
|
@@ -211,7 +218,7 @@ export class Abba {
|
|
|
211
218
|
*/
|
|
212
219
|
async getAllExistingUserAssignments(userId) {
|
|
213
220
|
const assignments = await this.userAssignmentDao.getBy('userId', userId);
|
|
214
|
-
return await pMap(assignments, async
|
|
221
|
+
return await pMap(assignments, async assignment => {
|
|
215
222
|
const experiment = await this.experimentDao.requireById(assignment.experimentId);
|
|
216
223
|
const bucket = await this.bucketDao.getById(assignment.bucketId);
|
|
217
224
|
return {
|
|
@@ -278,7 +285,7 @@ export class Abba {
|
|
|
278
285
|
async getExperimentAssignmentStatistics(experimentId) {
|
|
279
286
|
const totalAssignments = await this.userAssignmentDao.getCountByExperimentId(experimentId);
|
|
280
287
|
const buckets = await this.bucketDao.getByExperimentId(experimentId);
|
|
281
|
-
const bucketAssignments = await pMap(buckets, async
|
|
288
|
+
const bucketAssignments = await pMap(buckets, async bucket => {
|
|
282
289
|
const totalAssignments = await this.userAssignmentDao.getCountByBucketId(bucket.id);
|
|
283
290
|
return {
|
|
284
291
|
bucketId: bucket.id,
|
|
@@ -47,7 +47,7 @@ export function experimentDao(db) {
|
|
|
47
47
|
* For simplicity let's not do that by having this function...
|
|
48
48
|
*/
|
|
49
49
|
function parseMySQLDate(date) {
|
|
50
|
-
// @ts-expect-error
|
|
50
|
+
// @ts-expect-error MySQL may return Date instead of string
|
|
51
51
|
if (date instanceof Date)
|
|
52
52
|
return localDate(date).toISODate();
|
|
53
53
|
return date;
|
package/dist/types.d.ts
CHANGED
|
@@ -39,6 +39,18 @@ export type Experiment = BaseExperiment & {
|
|
|
39
39
|
exclusions: string[];
|
|
40
40
|
data: AnyObject | null;
|
|
41
41
|
};
|
|
42
|
+
export interface ExperimentInput {
|
|
43
|
+
id?: string;
|
|
44
|
+
key: string;
|
|
45
|
+
status: AssignmentStatus;
|
|
46
|
+
sampling: number;
|
|
47
|
+
description?: string | null;
|
|
48
|
+
rules: SegmentationRule[] | null;
|
|
49
|
+
startDateIncl: IsoDate;
|
|
50
|
+
endDateExcl: IsoDate;
|
|
51
|
+
exclusions: string[];
|
|
52
|
+
data: AnyObject | null;
|
|
53
|
+
}
|
|
42
54
|
export type ExperimentWithBuckets = Experiment & {
|
|
43
55
|
buckets: Bucket[];
|
|
44
56
|
};
|
|
@@ -50,6 +62,13 @@ export type BaseBucket = BaseDBEntity & {
|
|
|
50
62
|
export type Bucket = BaseBucket & {
|
|
51
63
|
data: AnyObject | null;
|
|
52
64
|
};
|
|
65
|
+
export interface BucketInput {
|
|
66
|
+
id?: string;
|
|
67
|
+
key: string;
|
|
68
|
+
ratio: number;
|
|
69
|
+
data: AnyObject | null;
|
|
70
|
+
experimentId?: string;
|
|
71
|
+
}
|
|
53
72
|
export type UserAssignment = BaseDBEntity & {
|
|
54
73
|
userId: string;
|
|
55
74
|
experimentId: string;
|
|
@@ -88,7 +107,9 @@ export declare enum SegmentationRuleOperator {
|
|
|
88
107
|
NotEqualsText = "notEqualsText",
|
|
89
108
|
Semver = "semver",
|
|
90
109
|
Regex = "regex",
|
|
91
|
-
Boolean = "boolean"
|
|
110
|
+
Boolean = "boolean",
|
|
111
|
+
GreaterThan = "greaterThan",
|
|
112
|
+
LessThan = "lessThan"
|
|
92
113
|
}
|
|
93
114
|
export type SegmentationRuleFn = (segmentationProp: string | boolean | number | null | undefined, ruleValue: SegmentationRule['value']) => boolean;
|
|
94
115
|
export interface ExperimentAssignmentStatistics {
|
package/dist/types.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { AssignmentStatus };
|
|
2
|
+
var AssignmentStatus;
|
|
2
3
|
(function (AssignmentStatus) {
|
|
3
4
|
/**
|
|
4
5
|
* Will return existing assignments and generate new assignments
|
|
@@ -13,7 +14,8 @@ export var AssignmentStatus;
|
|
|
13
14
|
*/
|
|
14
15
|
AssignmentStatus[AssignmentStatus["Inactive"] = 3] = "Inactive";
|
|
15
16
|
})(AssignmentStatus || (AssignmentStatus = {}));
|
|
16
|
-
export
|
|
17
|
+
export { SegmentationRuleOperator };
|
|
18
|
+
var SegmentationRuleOperator;
|
|
17
19
|
(function (SegmentationRuleOperator) {
|
|
18
20
|
SegmentationRuleOperator["IsSet"] = "isSet";
|
|
19
21
|
SegmentationRuleOperator["IsNotSet"] = "isNotSet";
|
|
@@ -21,6 +23,7 @@ export var SegmentationRuleOperator;
|
|
|
21
23
|
SegmentationRuleOperator["NotEqualsText"] = "notEqualsText";
|
|
22
24
|
SegmentationRuleOperator["Semver"] = "semver";
|
|
23
25
|
SegmentationRuleOperator["Regex"] = "regex";
|
|
24
|
-
/* eslint-disable id-blacklist*/
|
|
25
26
|
SegmentationRuleOperator["Boolean"] = "boolean";
|
|
27
|
+
SegmentationRuleOperator["GreaterThan"] = "greaterThan";
|
|
28
|
+
SegmentationRuleOperator["LessThan"] = "lessThan";
|
|
26
29
|
})(SegmentationRuleOperator || (SegmentationRuleOperator = {}));
|
package/dist/util.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Unsaved } from '@naturalcycles/js-lib/types';
|
|
2
|
-
import type { Bucket, ExclusionSet, Experiment, ExperimentWithBuckets, SegmentationData, SegmentationRule, SegmentationRuleFn, UserAssignment, UserExperiment } from './types.js';
|
|
2
|
+
import type { Bucket, BucketInput, ExclusionSet, Experiment, ExperimentWithBuckets, SegmentationData, SegmentationRule, SegmentationRuleFn, UserAssignment, UserExperiment } from './types.js';
|
|
3
3
|
import { SegmentationRuleOperator } from './types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Generate a new assignment for a given user.
|
|
@@ -24,7 +24,7 @@ export declare function determineBucket(buckets: Bucket[]): Bucket;
|
|
|
24
24
|
/**
|
|
25
25
|
* Validate the total ratio of the buckets equals 100
|
|
26
26
|
*/
|
|
27
|
-
export declare function validateTotalBucketRatio(buckets: Unsaved<Bucket>[]): void;
|
|
27
|
+
export declare function validateTotalBucketRatio(buckets: (Unsaved<Bucket> | BucketInput)[]): void;
|
|
28
28
|
/**
|
|
29
29
|
* Validate a users segmentation data against multiple rules. Returns false if any fail
|
|
30
30
|
*
|
package/dist/util.js
CHANGED
|
@@ -111,6 +111,16 @@ export const segmentationRuleMap = {
|
|
|
111
111
|
// Anything else cannot be true
|
|
112
112
|
return keyValue?.toString() !== 'true';
|
|
113
113
|
},
|
|
114
|
+
[SegmentationRuleOperator.GreaterThan](keyValue, ruleValue) {
|
|
115
|
+
if (keyValue === null || keyValue === undefined)
|
|
116
|
+
return false;
|
|
117
|
+
return keyValue > ruleValue;
|
|
118
|
+
},
|
|
119
|
+
[SegmentationRuleOperator.LessThan](keyValue, ruleValue) {
|
|
120
|
+
if (keyValue === null || keyValue === undefined)
|
|
121
|
+
return false;
|
|
122
|
+
return keyValue < ruleValue;
|
|
123
|
+
},
|
|
114
124
|
};
|
|
115
125
|
/**
|
|
116
126
|
* Returns true if an experiment is able to generate new assignments based on status and start/end dates
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/abba",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.7.1",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@naturalcycles/db-lib": "^10",
|
|
7
7
|
"@naturalcycles/js-lib": "^15",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@types/semver": "^7",
|
|
14
|
-
"@
|
|
14
|
+
"@typescript/native-preview": "7.0.0-dev.20260301.1",
|
|
15
|
+
"@naturalcycles/dev-lib": "18.4.2"
|
|
15
16
|
},
|
|
16
17
|
"exports": {
|
|
17
18
|
".": "./dist/index.js"
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"directory": "packages/abba"
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|
|
38
|
-
"node": ">=
|
|
39
|
+
"node": ">=24.10.0"
|
|
39
40
|
},
|
|
40
41
|
"description": "AB test assignment configuration tool for Node.js",
|
|
41
42
|
"author": "Natural Cycles Team",
|
package/src/abba.ts
CHANGED
|
@@ -6,15 +6,18 @@ import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'
|
|
|
6
6
|
import type { Unsaved } from '@naturalcycles/js-lib/types'
|
|
7
7
|
import { LRUMemoCache } from '@naturalcycles/nodejs-lib/lruMemoCache'
|
|
8
8
|
import { bucketDao } from './dao/bucket.dao.js'
|
|
9
|
-
import {
|
|
9
|
+
import type { GetAllExperimentsOpts } from './dao/experiment.dao.js'
|
|
10
|
+
import { experimentDao } from './dao/experiment.dao.js'
|
|
10
11
|
import { userAssignmentDao } from './dao/userAssignment.dao.js'
|
|
11
12
|
import type {
|
|
12
13
|
AbbaConfig,
|
|
13
14
|
Bucket,
|
|
14
15
|
BucketAssignmentStatistics,
|
|
16
|
+
BucketInput,
|
|
15
17
|
DecoratedUserAssignment,
|
|
16
18
|
Experiment,
|
|
17
19
|
ExperimentAssignmentStatistics,
|
|
20
|
+
ExperimentInput,
|
|
18
21
|
ExperimentWithBuckets,
|
|
19
22
|
SegmentationData,
|
|
20
23
|
UserAssignment,
|
|
@@ -117,13 +120,20 @@ export class Abba {
|
|
|
117
120
|
* Cold method.
|
|
118
121
|
*/
|
|
119
122
|
async createExperiment(
|
|
120
|
-
|
|
121
|
-
buckets:
|
|
123
|
+
input: ExperimentInput,
|
|
124
|
+
buckets: BucketInput[],
|
|
122
125
|
): Promise<ExperimentWithBuckets> {
|
|
123
|
-
if (
|
|
126
|
+
if (input.status === AssignmentStatus.Active) {
|
|
124
127
|
validateTotalBucketRatio(buckets)
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
const experiment = {
|
|
131
|
+
...input,
|
|
132
|
+
deleted: false,
|
|
133
|
+
description: input.description ?? null,
|
|
134
|
+
rules: input.rules ?? [],
|
|
135
|
+
} satisfies Unsaved<Experiment>
|
|
136
|
+
|
|
127
137
|
const created = await this.experimentDao.save(experiment)
|
|
128
138
|
const createdbuckets = await this.bucketDao.saveBatch(
|
|
129
139
|
buckets.map(b => ({ ...b, experimentId: created.id })),
|
|
@@ -56,7 +56,7 @@ export function experimentDao(db: CommonDB): ExperimentDao {
|
|
|
56
56
|
* For simplicity let's not do that by having this function...
|
|
57
57
|
*/
|
|
58
58
|
function parseMySQLDate(date: string): IsoDate {
|
|
59
|
-
// @ts-expect-error
|
|
59
|
+
// @ts-expect-error MySQL may return Date instead of string
|
|
60
60
|
if (date instanceof Date) return localDate(date).toISODate()
|
|
61
61
|
return date as IsoDate
|
|
62
62
|
}
|
package/src/types.ts
CHANGED
|
@@ -43,6 +43,19 @@ export type Experiment = BaseExperiment & {
|
|
|
43
43
|
data: AnyObject | null
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export interface ExperimentInput {
|
|
47
|
+
id?: string
|
|
48
|
+
key: string
|
|
49
|
+
status: AssignmentStatus
|
|
50
|
+
sampling: number
|
|
51
|
+
description?: string | null
|
|
52
|
+
rules: SegmentationRule[] | null
|
|
53
|
+
startDateIncl: IsoDate
|
|
54
|
+
endDateExcl: IsoDate
|
|
55
|
+
exclusions: string[]
|
|
56
|
+
data: AnyObject | null
|
|
57
|
+
}
|
|
58
|
+
|
|
46
59
|
export type ExperimentWithBuckets = Experiment & {
|
|
47
60
|
buckets: Bucket[]
|
|
48
61
|
}
|
|
@@ -57,6 +70,14 @@ export type Bucket = BaseBucket & {
|
|
|
57
70
|
data: AnyObject | null
|
|
58
71
|
}
|
|
59
72
|
|
|
73
|
+
export interface BucketInput {
|
|
74
|
+
id?: string
|
|
75
|
+
key: string
|
|
76
|
+
ratio: number
|
|
77
|
+
data: AnyObject | null
|
|
78
|
+
experimentId?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
export type UserAssignment = BaseDBEntity & {
|
|
61
82
|
userId: string
|
|
62
83
|
experimentId: string
|
|
@@ -100,8 +121,9 @@ export enum SegmentationRuleOperator {
|
|
|
100
121
|
NotEqualsText = 'notEqualsText',
|
|
101
122
|
Semver = 'semver',
|
|
102
123
|
Regex = 'regex',
|
|
103
|
-
/* eslint-disable id-blacklist*/
|
|
104
124
|
Boolean = 'boolean',
|
|
125
|
+
GreaterThan = 'greaterThan',
|
|
126
|
+
LessThan = 'lessThan',
|
|
105
127
|
}
|
|
106
128
|
|
|
107
129
|
export type SegmentationRuleFn = (
|
package/src/util.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Unsaved } from '@naturalcycles/js-lib/types'
|
|
|
3
3
|
import { satisfies } from 'semver'
|
|
4
4
|
import type {
|
|
5
5
|
Bucket,
|
|
6
|
+
BucketInput,
|
|
6
7
|
ExclusionSet,
|
|
7
8
|
Experiment,
|
|
8
9
|
ExperimentWithBuckets,
|
|
@@ -87,7 +88,7 @@ export function determineBucket(buckets: Bucket[]): Bucket {
|
|
|
87
88
|
/**
|
|
88
89
|
* Validate the total ratio of the buckets equals 100
|
|
89
90
|
*/
|
|
90
|
-
export function validateTotalBucketRatio(buckets: Unsaved<Bucket>[]): void {
|
|
91
|
+
export function validateTotalBucketRatio(buckets: (Unsaved<Bucket> | BucketInput)[]): void {
|
|
91
92
|
const bucketSum = buckets.reduce((sum, current) => sum + current.ratio, 0)
|
|
92
93
|
if (bucketSum !== 100) {
|
|
93
94
|
throw new Error('Total bucket ratio must be 100 before you can activate an experiment')
|
|
@@ -140,6 +141,14 @@ export const segmentationRuleMap: Record<SegmentationRuleOperator, SegmentationR
|
|
|
140
141
|
// Anything else cannot be true
|
|
141
142
|
return keyValue?.toString() !== 'true'
|
|
142
143
|
},
|
|
144
|
+
[SegmentationRuleOperator.GreaterThan](keyValue, ruleValue) {
|
|
145
|
+
if (keyValue === null || keyValue === undefined) return false
|
|
146
|
+
return keyValue > ruleValue
|
|
147
|
+
},
|
|
148
|
+
[SegmentationRuleOperator.LessThan](keyValue, ruleValue) {
|
|
149
|
+
if (keyValue === null || keyValue === undefined) return false
|
|
150
|
+
return keyValue < ruleValue
|
|
151
|
+
},
|
|
143
152
|
}
|
|
144
153
|
|
|
145
154
|
/**
|
package/dist/migrations/init.sql
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
-- CreateTable
|
|
2
|
-
CREATE TABLE IF NOT EXISTS `Bucket` (
|
|
3
|
-
`id` VARCHAR(50) NOT NULL,
|
|
4
|
-
`experimentId` VARCHAR(50) NOT NULL,
|
|
5
|
-
`key` VARCHAR(10) NOT NULL,
|
|
6
|
-
`ratio` INTEGER NOT NULL,
|
|
7
|
-
`data` JSON NULL,
|
|
8
|
-
`created` INT NOT NULL,
|
|
9
|
-
`updated` INT NOT NULL,
|
|
10
|
-
|
|
11
|
-
PRIMARY KEY (`id`)
|
|
12
|
-
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
13
|
-
|
|
14
|
-
-- CreateTable
|
|
15
|
-
CREATE TABLE IF NOT EXISTS `Experiment` (
|
|
16
|
-
`id` VARCHAR(50) NOT NULL,
|
|
17
|
-
`key` VARCHAR(50) NOT NULL,
|
|
18
|
-
`status` INTEGER NOT NULL,
|
|
19
|
-
`sampling` INTEGER NOT NULL,
|
|
20
|
-
`description` VARCHAR(240) NULL,
|
|
21
|
-
`startDateIncl` DATE NOT NULL,
|
|
22
|
-
`endDateExcl` DATE NOT NULL,
|
|
23
|
-
`created` INT NOT NULL,
|
|
24
|
-
`updated` INT NOT NULL,
|
|
25
|
-
`rules` JSON NULL,
|
|
26
|
-
`exclusions` JSON NULL,
|
|
27
|
-
`data` JSON NULL,
|
|
28
|
-
`deleted` BOOLEAN NOT NULL DEFAULT FALSE,
|
|
29
|
-
|
|
30
|
-
PRIMARY KEY (`id`),
|
|
31
|
-
UNIQUE INDEX `key_unique` (`key`)
|
|
32
|
-
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
33
|
-
|
|
34
|
-
-- CreateTable
|
|
35
|
-
CREATE TABLE IF NOT EXISTS `UserAssignment` (
|
|
36
|
-
`id` VARCHAR(50) NOT NULL,
|
|
37
|
-
`userId` VARCHAR(191) NOT NULL,
|
|
38
|
-
`experimentId` VARCHAR(50) NOT NULL,
|
|
39
|
-
`bucketId` VARCHAR(50) NULL,
|
|
40
|
-
`created` INT NOT NULL,
|
|
41
|
-
`updated` INT NOT NULL,
|
|
42
|
-
|
|
43
|
-
UNIQUE INDEX `UserAssignment_userId_experimentId_key`(`userId`, `experimentId`),
|
|
44
|
-
PRIMARY KEY (`id`)
|
|
45
|
-
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
46
|
-
|
|
47
|
-
-- AddForeignKey
|
|
48
|
-
ALTER TABLE `Bucket` ADD CONSTRAINT `Bucket_experimentId_fkey` FOREIGN KEY (`experimentId`) REFERENCES `Experiment`(`id`);
|
|
49
|
-
|
|
50
|
-
-- AddForeignKey
|
|
51
|
-
ALTER TABLE `UserAssignment` ADD CONSTRAINT `UserAssignment_bucketId_fkey` FOREIGN KEY (`bucketId`) REFERENCES `Bucket`(`id`);
|
|
52
|
-
|
|
53
|
-
-- AddForeignKey
|
|
54
|
-
ALTER TABLE `UserAssignment` ADD CONSTRAINT `UserAssignment_experimentId_fkey` FOREIGN KEY (`experimentId`) REFERENCES `Experiment`(`id`);
|