@delmaredigital/payload-experiments 0.1.0-rc.6 → 0.2.0-rc.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/README.md +28 -0
- package/dist/admin/LayerUsage.d.ts +1 -1
- package/dist/admin/LayerUsage.js +23 -11
- package/dist/admin/MetricSourceSelect.js +2 -1
- package/dist/admin/TestsNavLink.d.ts +1 -0
- package/dist/admin/TestsNavLink.js +30 -0
- package/dist/admin/allocation.model.js +1 -1
- package/dist/admin/copy/actions.d.ts +0 -2
- package/dist/admin/copy/actions.js +0 -2
- package/dist/admin/copy/advanced.d.ts +55 -0
- package/dist/admin/copy/advanced.js +55 -0
- package/dist/admin/copy/armSelect.js +1 -1
- package/dist/admin/copy/arms.d.ts +0 -13
- package/dist/admin/copy/arms.js +0 -13
- package/dist/admin/copy/{who.d.ts → audience.d.ts} +1 -9
- package/dist/admin/copy/{who.js → audience.js} +1 -9
- package/dist/admin/copy/history.d.ts +0 -4
- package/dist/admin/copy/history.js +3 -7
- package/dist/admin/copy/metrics.d.ts +2 -2
- package/dist/admin/copy/metrics.js +2 -2
- package/dist/admin/copy/newTest.d.ts +32 -0
- package/dist/admin/copy/newTest.js +32 -0
- package/dist/admin/copy/readiness.d.ts +45 -0
- package/dist/admin/copy/readiness.js +50 -0
- package/dist/admin/copy/recommendation.d.ts +26 -0
- package/dist/admin/copy/recommendation.js +25 -0
- package/dist/admin/copy/results.d.ts +0 -1
- package/dist/admin/copy/results.js +0 -1
- package/dist/admin/copy/review.d.ts +0 -17
- package/dist/admin/copy/review.js +1 -112
- package/dist/admin/copy/setting.d.ts +10 -0
- package/dist/admin/copy/setting.js +10 -0
- package/dist/admin/copy/surface.d.ts +36 -0
- package/dist/admin/copy/surface.js +47 -0
- package/dist/admin/copy/testPage.d.ts +88 -0
- package/dist/admin/copy/testPage.js +88 -0
- package/dist/admin/paths.d.ts +5 -13
- package/dist/admin/paths.js +5 -13
- package/dist/admin/review/review.model.d.ts +3 -26
- package/dist/admin/review/review.model.js +3 -30
- package/dist/admin/surface/ResultsRoute.d.ts +7 -0
- package/dist/admin/surface/ResultsRoute.js +58 -0
- package/dist/admin/surface/SurfaceShell.d.ts +6 -0
- package/dist/admin/surface/SurfaceShell.js +35 -0
- package/dist/admin/surface/TestsHome.d.ts +4 -0
- package/dist/admin/surface/TestsHome.js +212 -0
- package/dist/admin/surface/TestsSurface.d.ts +2 -0
- package/dist/admin/surface/TestsSurface.js +36 -0
- package/dist/admin/surface/contracts.d.ts +48 -0
- package/dist/admin/surface/contracts.js +8 -0
- package/dist/admin/surface/home.model.d.ts +41 -0
- package/dist/admin/surface/home.model.js +59 -0
- package/dist/admin/surface/newTest/NewTest.d.ts +7 -0
- package/dist/admin/surface/newTest/NewTest.js +355 -0
- package/dist/admin/surface/newTest/newTest.css +9 -0
- package/dist/admin/surface/pageBlock/pageBlock.model.d.ts +6 -0
- package/dist/admin/surface/pageBlock/pageBlock.model.js +8 -0
- package/dist/admin/surface/pageBlock/parts.d.ts +2 -0
- package/dist/admin/surface/pageBlock/parts.js +64 -0
- package/dist/admin/surface/readiness/ActionDialogs.d.ts +15 -0
- package/dist/admin/surface/readiness/ActionDialogs.js +274 -0
- package/dist/admin/surface/readiness/Banner.d.ts +2 -0
- package/dist/admin/surface/readiness/Banner.js +138 -0
- package/dist/admin/surface/readiness/SaveStatus.d.ts +4 -0
- package/dist/admin/surface/readiness/SaveStatus.js +50 -0
- package/dist/admin/surface/readiness/parts.d.ts +2 -0
- package/dist/admin/surface/readiness/parts.js +9 -0
- package/dist/admin/surface/readiness/readiness.model.d.ts +22 -0
- package/dist/admin/surface/readiness/readiness.model.js +107 -0
- package/dist/admin/surface/readiness/useTransition.d.ts +10 -0
- package/dist/admin/surface/readiness/useTransition.js +34 -0
- package/dist/admin/surface/results/ResultsBody.d.ts +7 -0
- package/dist/admin/surface/results/ResultsBody.js +262 -0
- package/dist/admin/surface/results/recommendation.model.d.ts +12 -0
- package/dist/admin/surface/results/recommendation.model.js +59 -0
- package/dist/admin/surface/results/useResults.d.ts +56 -0
- package/dist/admin/surface/results/useResults.js +183 -0
- package/dist/admin/surface/setting/parts.d.ts +3 -0
- package/dist/admin/surface/setting/parts.js +263 -0
- package/dist/admin/surface/setting/setting.css +29 -0
- package/dist/admin/surface/setting/setting.model.d.ts +8 -0
- package/dist/admin/surface/setting/setting.model.js +25 -0
- package/dist/admin/surface/surface.css +50 -0
- package/dist/admin/surface/test/AdvancedSection.d.ts +2 -0
- package/dist/admin/surface/test/AdvancedSection.js +153 -0
- package/dist/admin/surface/test/GoalSection.d.ts +2 -0
- package/dist/admin/surface/test/GoalSection.js +198 -0
- package/dist/admin/surface/test/LengthSection.d.ts +2 -0
- package/dist/admin/surface/test/LengthSection.js +87 -0
- package/dist/admin/surface/test/Section.d.ts +7 -0
- package/dist/admin/surface/test/Section.js +27 -0
- package/dist/admin/surface/test/TargetingSection.d.ts +2 -0
- package/dist/admin/surface/test/TargetingSection.js +164 -0
- package/dist/admin/surface/test/TestHeader.d.ts +4 -0
- package/dist/admin/surface/test/TestHeader.js +69 -0
- package/dist/admin/surface/test/TestPage.d.ts +7 -0
- package/dist/admin/surface/test/TestPage.js +84 -0
- package/dist/admin/surface/test/VersionsSection.d.ts +4 -0
- package/dist/admin/surface/test/VersionsSection.js +208 -0
- package/dist/admin/surface/test/fields.d.ts +55 -0
- package/dist/admin/surface/test/fields.js +185 -0
- package/dist/admin/surface/test/pageParts.d.ts +2 -0
- package/dist/admin/surface/test/pageParts.js +39 -0
- package/dist/admin/surface/test/testPage.css +23 -0
- package/dist/admin/surface/test/testPage.model.d.ts +17 -0
- package/dist/admin/surface/test/testPage.model.js +53 -0
- package/dist/admin/surface/testDocument.model.d.ts +39 -0
- package/dist/admin/surface/testDocument.model.js +61 -0
- package/dist/admin/surface/useTestDocument.d.ts +22 -0
- package/dist/admin/surface/useTestDocument.js +345 -0
- package/dist/admin/targeting/targeting.model.js +2 -2
- package/dist/admin/useLayerUsage.d.ts +0 -5
- package/dist/admin/useLayerUsage.js +1 -39
- package/dist/admin/views/CollectionEditRedirect.d.ts +4 -0
- package/dist/admin/views/CollectionEditRedirect.js +14 -0
- package/dist/admin/views/CollectionListRedirect.d.ts +2 -0
- package/dist/admin/views/CollectionListRedirect.js +8 -0
- package/dist/admin/views/ExperimentsRootView.d.ts +2 -0
- package/dist/admin/views/ExperimentsRootView.js +175 -0
- package/dist/admin/views/access.model.d.ts +7 -0
- package/dist/admin/views/access.model.js +5 -0
- package/dist/collections/experiments/config.js +18 -125
- package/dist/collections/experiments/createDefaults.js +6 -0
- package/dist/collections/experiments/flagControlParams.js +8 -14
- package/dist/collections/layers.js +45 -13
- package/dist/collections/metrics.js +70 -14
- package/dist/collections/serialize.js +4 -1
- package/dist/db/snapshots.d.ts +2 -1
- package/dist/db/snapshots.js +2 -2
- package/dist/endpoints/results.js +7 -0
- package/dist/endpoints/summary.d.ts +1 -1
- package/dist/endpoints/summary.js +37 -1
- package/dist/exports/adminClient.d.ts +2 -13
- package/dist/exports/adminClient.js +2 -13
- package/dist/exports/index.d.ts +1 -1
- package/dist/exports/rsc.d.ts +3 -0
- package/dist/exports/rsc.js +3 -0
- package/dist/options.js +29 -0
- package/dist/plugin.js +25 -0
- package/dist/puck/editor/CreateExperimentDrawer.d.ts +2 -1
- package/dist/puck/editor/CreateExperimentDrawer.js +17 -57
- package/dist/puck/editor/ExperimentSlotEditor.js +6 -0
- package/dist/puck/editor/copy.d.ts +10 -9
- package/dist/puck/editor/copy.js +11 -10
- package/dist/puck/editor/createTest.d.ts +7 -0
- package/dist/puck/editor/createTest.js +50 -0
- package/dist/puck/editor/editor.model.d.ts +28 -1
- package/dist/puck/editor/editor.model.js +61 -0
- package/dist/results/analyse.js +4 -1
- package/dist/results/verdict.d.ts +1 -0
- package/dist/results/verdict.js +7 -4
- package/dist/transitions/checkStart.d.ts +1 -0
- package/dist/transitions/placements.d.ts +5 -1
- package/dist/transitions/placements.js +14 -7
- package/dist/transitions/preflightData.d.ts +7 -1
- package/dist/transitions/preflightData.js +199 -148
- package/dist/transitions/start.js +2 -2
- package/dist/types/admin.d.ts +1 -0
- package/dist/types/contracts.d.ts +17 -0
- package/dist/types/endpoints.d.ts +11 -1
- package/dist/types/snapshot.d.ts +1 -0
- package/dist/types/surface.d.ts +29 -0
- package/dist/types/surface.js +1 -0
- package/package.json +1 -1
- package/dist/access.d.ts +0 -1
- package/dist/access.js +0 -2
- package/dist/admin/AllocationField.d.ts +0 -2
- package/dist/admin/AllocationField.js +0 -80
- package/dist/admin/FlagPointSelect.d.ts +0 -2
- package/dist/admin/FlagPointSelect.js +0 -54
- package/dist/admin/LockedFieldNotice.d.ts +0 -2
- package/dist/admin/LockedFieldNotice.js +0 -26
- package/dist/admin/PhaseHistory.d.ts +0 -2
- package/dist/admin/PhaseHistory.js +0 -76
- package/dist/admin/StatusCell.d.ts +0 -3
- package/dist/admin/StatusCell.js +0 -19
- package/dist/admin/SummaryCell.d.ts +0 -2
- package/dist/admin/SummaryCell.js +0 -32
- package/dist/admin/actions/AddMetricDialog.d.ts +0 -6
- package/dist/admin/actions/AddMetricDialog.js +0 -138
- package/dist/admin/actions/AllocateDialog.d.ts +0 -6
- package/dist/admin/actions/AllocateDialog.js +0 -81
- package/dist/admin/actions/ExperimentActions.d.ts +0 -3
- package/dist/admin/actions/ExperimentActions.js +0 -110
- package/dist/admin/arms/ArmsField.d.ts +0 -2
- package/dist/admin/arms/ArmsField.js +0 -310
- package/dist/admin/arms/ParamsEditor.d.ts +0 -16
- package/dist/admin/arms/ParamsEditor.js +0 -79
- package/dist/admin/copy/tabs.d.ts +0 -7
- package/dist/admin/copy/tabs.js +0 -7
- package/dist/admin/permissions.model.d.ts +0 -10
- package/dist/admin/permissions.model.js +0 -4
- package/dist/admin/phaseHistory.model.d.ts +0 -19
- package/dist/admin/phaseHistory.model.js +0 -19
- package/dist/admin/results/ResultsView.d.ts +0 -3
- package/dist/admin/results/ResultsView.js +0 -312
- package/dist/admin/review/ReviewPanel.d.ts +0 -2
- package/dist/admin/review/ReviewPanel.js +0 -268
- package/dist/admin/summaryBatcher.d.ts +0 -8
- package/dist/admin/summaryBatcher.js +0 -33
- package/dist/admin/targeting/TargetingAttributeField.d.ts +0 -2
- package/dist/admin/targeting/TargetingAttributeField.js +0 -31
- package/dist/admin/targeting/TargetingOpField.d.ts +0 -2
- package/dist/admin/targeting/TargetingOpField.js +0 -45
- package/dist/admin/targeting/TargetingValueField.d.ts +0 -2
- package/dist/admin/targeting/TargetingValueField.js +0 -142
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -4
|
@@ -7,7 +7,13 @@ export function metricsCollection(options) {
|
|
|
7
7
|
slug: 'xp-metrics',
|
|
8
8
|
graphQL: false,
|
|
9
9
|
disableDuplicate: true,
|
|
10
|
+
// group:false hides navigation while keeping collection routes reachable (hidden causes 404s).
|
|
11
|
+
labels: {
|
|
12
|
+
singular: 'Goal',
|
|
13
|
+
plural: 'Goals'
|
|
14
|
+
},
|
|
10
15
|
admin: {
|
|
16
|
+
group: false,
|
|
11
17
|
useAsTitle: 'name'
|
|
12
18
|
},
|
|
13
19
|
access: managedAccess(options),
|
|
@@ -30,21 +36,31 @@ export function metricsCollection(options) {
|
|
|
30
36
|
fields: [
|
|
31
37
|
{
|
|
32
38
|
name: 'name',
|
|
39
|
+
label: 'Name',
|
|
33
40
|
type: 'text',
|
|
34
|
-
required: true
|
|
41
|
+
required: true,
|
|
42
|
+
admin: {
|
|
43
|
+
description: 'Give this goal a name people will recognize.'
|
|
44
|
+
}
|
|
35
45
|
},
|
|
36
46
|
{
|
|
37
47
|
name: 'slug',
|
|
48
|
+
label: 'Short name (used in code)',
|
|
38
49
|
type: 'text',
|
|
39
50
|
required: true,
|
|
40
|
-
unique: true
|
|
51
|
+
unique: true,
|
|
52
|
+
admin: {
|
|
53
|
+
description: 'Use a unique short name for code that refers to this goal.'
|
|
54
|
+
}
|
|
41
55
|
},
|
|
42
56
|
{
|
|
43
57
|
name: 'source',
|
|
58
|
+
label: 'Measured by',
|
|
44
59
|
type: 'text',
|
|
45
60
|
required: true,
|
|
46
|
-
validate: (value)=>sources.some((source)=>source.id === value) || 'Choose
|
|
61
|
+
validate: (value)=>sources.some((source)=>source.id === value) || 'Choose what this goal is measured by.',
|
|
47
62
|
admin: {
|
|
63
|
+
description: 'Choose the outcome this goal measures.',
|
|
48
64
|
components: {
|
|
49
65
|
Field: XP_METRIC_SOURCE_SELECT
|
|
50
66
|
},
|
|
@@ -55,60 +71,100 @@ export function metricsCollection(options) {
|
|
|
55
71
|
},
|
|
56
72
|
{
|
|
57
73
|
name: 'direction',
|
|
74
|
+
label: 'Better when it goes',
|
|
58
75
|
type: 'select',
|
|
59
76
|
required: true,
|
|
60
77
|
defaultValue: 'increase',
|
|
61
78
|
options: [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
79
|
+
{
|
|
80
|
+
label: 'Goes up',
|
|
81
|
+
value: 'increase'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: 'Goes down',
|
|
85
|
+
value: 'decrease'
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
admin: {
|
|
89
|
+
description: 'Choose whether a higher or lower result is better.'
|
|
90
|
+
}
|
|
65
91
|
},
|
|
66
92
|
{
|
|
67
93
|
name: 'attributionWindowHours',
|
|
94
|
+
label: 'Count results for (hours)',
|
|
68
95
|
type: 'number',
|
|
69
96
|
defaultValue: 168,
|
|
70
|
-
min: 1
|
|
97
|
+
min: 1,
|
|
98
|
+
admin: {
|
|
99
|
+
description: 'Count outcomes during this many hours after someone sees a version.'
|
|
100
|
+
}
|
|
71
101
|
},
|
|
72
102
|
{
|
|
73
103
|
name: 'capping',
|
|
104
|
+
label: 'Limit very large values',
|
|
74
105
|
type: 'group',
|
|
106
|
+
admin: {
|
|
107
|
+
description: 'Limit unusually large amounts when comparing results while keeping totals in full.'
|
|
108
|
+
},
|
|
75
109
|
fields: [
|
|
76
110
|
{
|
|
77
111
|
name: 'type',
|
|
112
|
+
label: 'Limit by',
|
|
78
113
|
type: 'select',
|
|
79
114
|
options: [
|
|
80
|
-
|
|
81
|
-
|
|
115
|
+
{
|
|
116
|
+
label: 'Based on past amounts',
|
|
117
|
+
value: 'percentile'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
label: 'Fixed amount',
|
|
121
|
+
value: 'absolute'
|
|
122
|
+
}
|
|
82
123
|
],
|
|
124
|
+
admin: {
|
|
125
|
+
description: 'Choose whether the limit comes from past amounts or a fixed amount.'
|
|
126
|
+
},
|
|
83
127
|
validate: (value, { data, siblingData })=>{
|
|
84
128
|
const hasCapping = value != null || siblingData?.value != null;
|
|
85
129
|
const source = sources.find((source)=>source.id === data?.source);
|
|
86
|
-
if (hasCapping && source && source.kind !== 'revenue') return 'Count and
|
|
87
|
-
return !hasCapping || value === 'percentile' || value === 'absolute' || 'Choose
|
|
130
|
+
if (hasCapping && source && source.kind !== 'revenue') return 'Count and yes-or-no goals cannot use a limit.';
|
|
131
|
+
return !hasCapping || value === 'percentile' || value === 'absolute' || 'Choose how to set the limit.';
|
|
88
132
|
}
|
|
89
133
|
},
|
|
90
134
|
{
|
|
91
135
|
name: 'value',
|
|
136
|
+
label: 'Limit value',
|
|
92
137
|
type: 'number',
|
|
93
138
|
min: 0,
|
|
139
|
+
admin: {
|
|
140
|
+
description: 'For past amounts, enter how much of the usual range to keep, such as 0.99 to limit only the largest 1%; or enter a fixed amount.'
|
|
141
|
+
},
|
|
94
142
|
validate: (value, { siblingData })=>{
|
|
95
143
|
if (value == null && !siblingData?.type) return true;
|
|
96
|
-
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && (siblingData?.type === 'absolute' || siblingData?.type === 'percentile' && value > 0 && value <= 1) || 'Enter a valid
|
|
144
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && (siblingData?.type === 'absolute' || siblingData?.type === 'percentile' && value > 0 && value <= 1) || 'Enter a valid limit (for past amounts, use a number greater than 0 and at most 1).';
|
|
97
145
|
}
|
|
98
146
|
}
|
|
99
147
|
]
|
|
100
148
|
},
|
|
101
149
|
{
|
|
102
150
|
name: 'minSampleSize',
|
|
151
|
+
label: 'Minimum completions before deciding',
|
|
103
152
|
type: 'number',
|
|
104
153
|
defaultValue: 150,
|
|
105
|
-
min: 1
|
|
154
|
+
min: 1,
|
|
155
|
+
admin: {
|
|
156
|
+
description: 'Wait until each version has at least this many people who completed the goal before showing a result.'
|
|
157
|
+
}
|
|
106
158
|
},
|
|
107
159
|
{
|
|
108
160
|
name: 'guardrailThreshold',
|
|
161
|
+
label: 'Warn when it gets worse by',
|
|
109
162
|
type: 'number',
|
|
110
163
|
defaultValue: 0.05,
|
|
111
|
-
min: 0
|
|
164
|
+
min: 0,
|
|
165
|
+
admin: {
|
|
166
|
+
description: 'When this goal is a safety check, warn if it gets worse by this fraction, such as 0.05 for 5%.'
|
|
167
|
+
}
|
|
112
168
|
}
|
|
113
169
|
]
|
|
114
170
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export function serializeFlagPoints(options) {
|
|
2
|
-
return (options.flagPoints ?? []).map(({ id, label, params, defaultParams })=>({
|
|
2
|
+
return (options.flagPoints ?? []).map(({ id, label, description, params, defaultParams })=>({
|
|
3
3
|
id,
|
|
4
4
|
label,
|
|
5
|
+
...description === undefined ? {} : {
|
|
6
|
+
description
|
|
7
|
+
},
|
|
5
8
|
params,
|
|
6
9
|
defaultParams,
|
|
7
10
|
effectiveDefaults: defaultParams
|
package/dist/db/snapshots.d.ts
CHANGED
|
@@ -40,8 +40,9 @@ export declare function writeSnapshots(exec: Executor, snapshots: Snapshot[]): P
|
|
|
40
40
|
* with req/overrideAccess:false. Called behind the readResults/manage gate. */
|
|
41
41
|
export declare function newestSnapshotIds(exec: Executor, ids: DocId[], per: 'phase' | 'experiment'): Promise<DocId[]>;
|
|
42
42
|
/** Called only behind the endpoint's readResults gate. Experiment collection
|
|
43
|
-
* reads require manage, so read only the decision
|
|
43
|
+
* reads require manage, so read only the decision metadata needed by results. */
|
|
44
44
|
export declare function resultsMetadata(exec: Executor, id: DocId): Promise<{
|
|
45
45
|
status: string;
|
|
46
46
|
snapshotId: DocId | null;
|
|
47
|
+
winnerArm: string | null;
|
|
47
48
|
} | null>;
|
package/dist/db/snapshots.js
CHANGED
|
@@ -80,9 +80,9 @@ export async function writeSnapshot(exec, snapshot) {
|
|
|
80
80
|
return rows.map((row)=>row.id);
|
|
81
81
|
}
|
|
82
82
|
/** Called only behind the endpoint's readResults gate. Experiment collection
|
|
83
|
-
* reads require manage, so read only the decision
|
|
83
|
+
* reads require manage, so read only the decision metadata needed by results. */ export async function resultsMetadata(exec, id) {
|
|
84
84
|
const c = COLUMNS.experiments;
|
|
85
|
-
const { rows } = await exec.query(`SELECT ${q(c.status)} AS "status", ${q(c.decisionSnapshotId)} AS "snapshotId" FROM ${qualifiedTable(exec, TABLES.experiments)} WHERE ${q(c.id)} = $1`, [
|
|
85
|
+
const { rows } = await exec.query(`SELECT ${q(c.status)} AS "status", ${q(c.decisionSnapshotId)} AS "snapshotId", ${q(c.decisionWinnerArm)} AS "winnerArm" FROM ${qualifiedTable(exec, TABLES.experiments)} WHERE ${q(c.id)} = $1`, [
|
|
86
86
|
id
|
|
87
87
|
]);
|
|
88
88
|
return rows[0] ?? null;
|
|
@@ -116,6 +116,13 @@ export async function handleResults(req, _ctx) {
|
|
|
116
116
|
decisionSnapshot: decision
|
|
117
117
|
} : {}
|
|
118
118
|
};
|
|
119
|
+
const decisionPhase = decision?.phase ?? records.filter((record)=>record.phaseKind === 'test').at(-1)?.phase;
|
|
120
|
+
if (typeof metadata.winnerArm === 'string' && metadata.winnerArm.length > 0 && decisionPhase !== undefined) {
|
|
121
|
+
response.decision = {
|
|
122
|
+
phase: decisionPhase,
|
|
123
|
+
winnerArm: metadata.winnerArm
|
|
124
|
+
};
|
|
125
|
+
}
|
|
119
126
|
if (params.get('series') === '1') {
|
|
120
127
|
const series = phase === undefined ? [] : await readSnapshots(req, [
|
|
121
128
|
id
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { checkStart } from '../transitions/checkStart.js';
|
|
2
|
+
import { loadStartInputsBatch } from '../transitions/preflightData.js';
|
|
1
3
|
import { readSnapshots, resultsError } from './results.js';
|
|
2
|
-
export async function handleSummary(req,
|
|
4
|
+
export async function handleSummary(req, { options }) {
|
|
3
5
|
let ids;
|
|
4
6
|
try {
|
|
5
7
|
// Split before decoding: an opaque id can itself contain a comma.
|
|
@@ -32,6 +34,40 @@ export async function handleSummary(req, _ctx) {
|
|
|
32
34
|
verdict: null
|
|
33
35
|
};
|
|
34
36
|
}
|
|
37
|
+
try {
|
|
38
|
+
if (await options.access.manage({
|
|
39
|
+
req
|
|
40
|
+
}) === true) {
|
|
41
|
+
const found = await req.payload.find({
|
|
42
|
+
collection: 'xp-experiments',
|
|
43
|
+
req,
|
|
44
|
+
overrideAccess: false,
|
|
45
|
+
depth: 0,
|
|
46
|
+
pagination: false,
|
|
47
|
+
limit: ids.length,
|
|
48
|
+
where: {
|
|
49
|
+
id: {
|
|
50
|
+
in: [
|
|
51
|
+
...new Set(ids)
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const drafts = found.docs.filter((e)=>e.status === 'draft' || e.status === 'stopped');
|
|
57
|
+
const inputs = await loadStartInputsBatch(req, options, drafts, true);
|
|
58
|
+
for (const id of ids){
|
|
59
|
+
// Payload normalises numeric ids (for example 01 to 1); preserve the requested response key.
|
|
60
|
+
const experiment = drafts.find((e)=>String(e.id) === id || typeof e.id === 'number' && Number(id) === e.id);
|
|
61
|
+
if (!experiment) continue;
|
|
62
|
+
const input = inputs.get(String(experiment.id));
|
|
63
|
+
try {
|
|
64
|
+
response[id].ready = !!input && checkStart(input).every((check)=>check.ok);
|
|
65
|
+
} catch {
|
|
66
|
+
response[id].ready = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
35
71
|
return Response.json(response, {
|
|
36
72
|
headers: {
|
|
37
73
|
'Cache-Control': 'no-store'
|
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
export { AllocationField } from '../admin/AllocationField.js';
|
|
2
|
-
export { FlagPointSelect } from '../admin/FlagPointSelect.js';
|
|
3
1
|
export { LayerUsage } from '../admin/LayerUsage.js';
|
|
4
|
-
export { LockedFieldNotice } from '../admin/LockedFieldNotice.js';
|
|
5
2
|
export { MetricSourceSelect } from '../admin/MetricSourceSelect.js';
|
|
6
|
-
export { PhaseHistory } from '../admin/PhaseHistory.js';
|
|
7
|
-
export { StatusCell } from '../admin/StatusCell.js';
|
|
8
|
-
export { SummaryCell } from '../admin/SummaryCell.js';
|
|
9
|
-
export { ExperimentActions } from '../admin/actions/ExperimentActions.js';
|
|
10
3
|
export { ArmSelectField } from '../admin/armSelect/ArmSelectField.js';
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export { ReviewPanel } from '../admin/review/ReviewPanel.js';
|
|
14
|
-
export { TargetingAttributeField } from '../admin/targeting/TargetingAttributeField.js';
|
|
15
|
-
export { TargetingOpField } from '../admin/targeting/TargetingOpField.js';
|
|
16
|
-
export { TargetingValueField } from '../admin/targeting/TargetingValueField.js';
|
|
4
|
+
export { TestsNavLink } from '../admin/TestsNavLink.js';
|
|
5
|
+
export { TestsSurface } from '../admin/surface/TestsSurface.js';
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
export { AllocationField } from '../admin/AllocationField.js';
|
|
3
|
-
export { FlagPointSelect } from '../admin/FlagPointSelect.js';
|
|
4
2
|
export { LayerUsage } from '../admin/LayerUsage.js';
|
|
5
|
-
export { LockedFieldNotice } from '../admin/LockedFieldNotice.js';
|
|
6
3
|
export { MetricSourceSelect } from '../admin/MetricSourceSelect.js';
|
|
7
|
-
export { PhaseHistory } from '../admin/PhaseHistory.js';
|
|
8
|
-
export { StatusCell } from '../admin/StatusCell.js';
|
|
9
|
-
export { SummaryCell } from '../admin/SummaryCell.js';
|
|
10
|
-
export { ExperimentActions } from '../admin/actions/ExperimentActions.js';
|
|
11
4
|
export { ArmSelectField } from '../admin/armSelect/ArmSelectField.js';
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export { ReviewPanel } from '../admin/review/ReviewPanel.js';
|
|
15
|
-
export { TargetingAttributeField } from '../admin/targeting/TargetingAttributeField.js';
|
|
16
|
-
export { TargetingOpField } from '../admin/targeting/TargetingOpField.js';
|
|
17
|
-
export { TargetingValueField } from '../admin/targeting/TargetingValueField.js';
|
|
5
|
+
export { TestsNavLink } from '../admin/TestsNavLink.js';
|
|
6
|
+
export { TestsSurface } from '../admin/surface/TestsSurface.js';
|
package/dist/exports/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { createExperimentsPlugin } from '../plugin.js';
|
|
2
2
|
export { defineFlagPoint, defineMetricSource } from '../define.js';
|
|
3
3
|
export { createArmSelectField } from '../fields/armSelect.js';
|
|
4
|
-
export type { SerializedFlagPoint, SerializedMetricSource, SerializedAttributeKeys, EditableField, Assignment, Assignments, FlagPointResult, ClientAssignments, RenderedExposure, DocId, Attributes, ExposurePolicy, ExperimentKind, Status, UnitType, ArmKey, ArmKind, TargetingRule, Arm, ParamField, MetricSource, FlagPoint, RateLimiter, ExperimentsAccess, ExperimentsPluginOptions, ArmIssueCode, RefusalCode, ErrorEnvelope, TransitionSuccess, AllocateBody, AddMetricBody, DecideBody, ArchiveBody, MakeContentResult, PreflightResult, PreviewTokenResult, PhaseSummary, ResultsResponse, SummaryResponse, EventBody, FlagDefault, Manifest, ManifestExperiment, ArmSlot, ExperimentRef, ExperimentSlotProps, ExperimentsProxyRuntimeOptions, ExperimentsProxyRuntime, ExperimentsServerRuntimeOptions, ExperimentsServerRuntime, VerdictCode, Snapshot, } from '../types/index.js';
|
|
4
|
+
export type { SerializedFlagPoint, SerializedMetricSource, SerializedAttributeKeys, EditableField, Assignment, Assignments, FlagPointResult, ClientAssignments, RenderedExposure, DocId, Attributes, ExposurePolicy, ExperimentKind, Status, UnitType, ArmKey, ArmKind, TargetingRule, Arm, ParamField, MetricSource, FlagPoint, RateLimiter, ExperimentsAccess, ExperimentsPluginOptions, ArmIssueCode, RefusalCode, ReadinessCode, ErrorEnvelope, TransitionSuccess, AllocateBody, AddMetricBody, DecideBody, ArchiveBody, MakeContentResult, PreflightResult, PreviewTokenResult, PhaseSummary, ResultsResponse, SummaryResponse, EventBody, FlagDefault, Manifest, ManifestExperiment, ArmSlot, ExperimentRef, ExperimentSlotProps, ExperimentsProxyRuntimeOptions, ExperimentsProxyRuntime, ExperimentsServerRuntimeOptions, ExperimentsServerRuntime, VerdictCode, Snapshot, } from '../types/index.js';
|
package/dist/exports/rsc.d.ts
CHANGED
|
@@ -11,3 +11,6 @@ export { experimentSitemapFilter } from '../server/sitemap.js';
|
|
|
11
11
|
export { experimentMetadata } from '../server/metadata.js';
|
|
12
12
|
export { toClientAssignments } from '../server/toClientAssignments.js';
|
|
13
13
|
export { contentChangesBeforeHook as captureContentBaseline } from '../hooks/contentChanges.js';
|
|
14
|
+
export { ExperimentsRootView } from '../admin/views/ExperimentsRootView.js';
|
|
15
|
+
export { CollectionListRedirect } from '../admin/views/CollectionListRedirect.js';
|
|
16
|
+
export { CollectionEditRedirect } from '../admin/views/CollectionEditRedirect.js';
|
package/dist/exports/rsc.js
CHANGED
|
@@ -11,3 +11,6 @@ export { experimentSitemapFilter } from '../server/sitemap.js';
|
|
|
11
11
|
export { experimentMetadata } from '../server/metadata.js';
|
|
12
12
|
export { toClientAssignments } from '../server/toClientAssignments.js';
|
|
13
13
|
export { contentChangesBeforeHook as captureContentBaseline } from '../hooks/contentChanges.js';
|
|
14
|
+
export { ExperimentsRootView } from '../admin/views/ExperimentsRootView.js';
|
|
15
|
+
export { CollectionListRedirect } from '../admin/views/CollectionListRedirect.js';
|
|
16
|
+
export { CollectionEditRedirect } from '../admin/views/CollectionEditRedirect.js';
|
package/dist/options.js
CHANGED
|
@@ -26,9 +26,38 @@ export function resolveOptions(input) {
|
|
|
26
26
|
})){
|
|
27
27
|
if (typeof access !== 'function') throw new Error(`Experiments access.${name} must be a function`);
|
|
28
28
|
}
|
|
29
|
+
if (input.admin !== undefined) {
|
|
30
|
+
const admin = input.admin;
|
|
31
|
+
if (!admin || typeof admin !== 'object' || Array.isArray(admin)) throw new Error('Experiments admin must be an object');
|
|
32
|
+
for (const key of Object.keys(admin)){
|
|
33
|
+
if (![
|
|
34
|
+
'editorUrl',
|
|
35
|
+
'previewUrl',
|
|
36
|
+
'descriptions'
|
|
37
|
+
].includes(key)) throw new Error(`Unknown experiments admin option: ${key}`);
|
|
38
|
+
}
|
|
39
|
+
for (const key of [
|
|
40
|
+
'editorUrl',
|
|
41
|
+
'previewUrl'
|
|
42
|
+
]){
|
|
43
|
+
if (admin[key] !== undefined && typeof admin[key] !== 'function') throw new Error(`Experiments admin.${key} must be a function`);
|
|
44
|
+
}
|
|
45
|
+
if (admin.descriptions !== undefined) {
|
|
46
|
+
if (!admin.descriptions || typeof admin.descriptions !== 'object' || Array.isArray(admin.descriptions)) throw new Error('Experiments admin.descriptions must be an object');
|
|
47
|
+
for (const [key, value] of Object.entries(admin.descriptions)){
|
|
48
|
+
if (![
|
|
49
|
+
'block',
|
|
50
|
+
'document',
|
|
51
|
+
'setting'
|
|
52
|
+
].includes(key)) throw new Error(`Unknown experiments admin.descriptions option: ${key}`);
|
|
53
|
+
if (value !== undefined && typeof value !== 'string') throw new Error(`Experiments admin.descriptions.${key} must be a string`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
29
57
|
const ids = new Set();
|
|
30
58
|
for (const point of input.flagPoints ?? []){
|
|
31
59
|
if (typeof point.id !== 'string' || !point.id || ids.has(point.id)) throw new Error('Experiments flag point ids must be unique nonempty strings');
|
|
60
|
+
if (point.description !== undefined && typeof point.description !== 'string') throw new Error('Experiments flag point description must be a string');
|
|
32
61
|
ids.add(point.id);
|
|
33
62
|
}
|
|
34
63
|
// Record keys are unique by construction; validate each declared key and type.
|
package/dist/plugin.js
CHANGED
|
@@ -3,6 +3,7 @@ import { buildEndpoints } from './endpoints/index.js';
|
|
|
3
3
|
import { contentChangesBeforeHook, contentChangesHook } from './hooks/contentChanges.js';
|
|
4
4
|
import { createOnInit } from './onInit.js';
|
|
5
5
|
import { puckCollections, resolveOptions } from './options.js';
|
|
6
|
+
import * as paths from './admin/paths.js';
|
|
6
7
|
export function createExperimentsPlugin(input) {
|
|
7
8
|
const options = resolveOptions(input);
|
|
8
9
|
return (config)=>{
|
|
@@ -16,6 +17,30 @@ export function createExperimentsPlugin(input) {
|
|
|
16
17
|
const onInit = createOnInit(options);
|
|
17
18
|
return {
|
|
18
19
|
...config,
|
|
20
|
+
admin: {
|
|
21
|
+
...config.admin,
|
|
22
|
+
dependencies: {
|
|
23
|
+
...config.admin?.dependencies,
|
|
24
|
+
xpTestsSurface: {
|
|
25
|
+
path: paths.XP_TESTS_SURFACE,
|
|
26
|
+
type: 'component'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
components: {
|
|
30
|
+
...config.admin?.components,
|
|
31
|
+
views: {
|
|
32
|
+
...config.admin?.components?.views,
|
|
33
|
+
xpTests: {
|
|
34
|
+
Component: paths.XP_TESTS_VIEW,
|
|
35
|
+
path: '/experiments/:segments*'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
afterNavLinks: [
|
|
39
|
+
...config.admin?.components?.afterNavLinks ?? [],
|
|
40
|
+
paths.XP_TESTS_NAV_LINK
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
19
44
|
collections: [
|
|
20
45
|
...(config.collections ?? []).map((collection)=>{
|
|
21
46
|
const puck = options.puck?.enabled && registrations.some((entry)=>entry.slug === collection.slug);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExperimentRef } from '../../types/puck.js';
|
|
2
|
-
export declare function CreateExperimentDrawer({ onCreate, onClose }: {
|
|
2
|
+
export declare function CreateExperimentDrawer({ onCreate, onClose, initialName }: {
|
|
3
|
+
initialName?: string;
|
|
3
4
|
onCreate: (experiment: ExperimentRef) => void;
|
|
4
5
|
onClose: () => void;
|
|
5
6
|
}): import("react").JSX.Element;
|
|
@@ -1,50 +1,31 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useId, useState } from 'react';
|
|
4
|
-
import {
|
|
3
|
+
import { useId, useRef, useState } from 'react';
|
|
4
|
+
import { CreateRefused, createTest } from './createTest.js';
|
|
5
5
|
import { editorCopy } from './copy.js';
|
|
6
|
-
export function CreateExperimentDrawer({ onCreate, onClose }) {
|
|
6
|
+
export function CreateExperimentDrawer({ onCreate, onClose, initialName = '' }) {
|
|
7
7
|
const titleId = useId();
|
|
8
|
-
const [name, setName] = useState(
|
|
9
|
-
const
|
|
10
|
-
const [slugEdited, setSlugEdited] = useState(false);
|
|
8
|
+
const [name, setName] = useState(initialName);
|
|
9
|
+
const inFlight = useRef(false);
|
|
11
10
|
const [pending, setPending] = useState(false);
|
|
12
11
|
const [error, setError] = useState('');
|
|
13
12
|
async function submitNew() {
|
|
13
|
+
if (inFlight.current || !name.trim()) return;
|
|
14
|
+
inFlight.current = true;
|
|
14
15
|
setPending(true);
|
|
15
16
|
setError('');
|
|
16
17
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'Content-Type': 'application/json'
|
|
22
|
-
},
|
|
23
|
-
body: JSON.stringify({
|
|
24
|
-
name,
|
|
25
|
-
slug,
|
|
26
|
-
kind: 'block',
|
|
27
|
-
unitType: 'visitor'
|
|
28
|
-
})
|
|
29
|
-
});
|
|
30
|
-
const body = await response.json();
|
|
31
|
-
if (!response.ok) {
|
|
32
|
-
setError(typeof body?.errors?.[0]?.message === 'string' ? body.errors[0].message : editorCopy.createError);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const doc = body?.doc;
|
|
36
|
-
if (!doc || typeof doc.id !== 'string' && typeof doc.id !== 'number' || typeof doc.slug !== 'string') {
|
|
37
|
-
setError(editorCopy.createError);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
onCreate({
|
|
41
|
-
id: doc.id,
|
|
42
|
-
slug: doc.slug
|
|
18
|
+
const experiment = await createTest('/api', {
|
|
19
|
+
name,
|
|
20
|
+
kind: 'block',
|
|
21
|
+
unitType: 'visitor'
|
|
43
22
|
});
|
|
23
|
+
onCreate(experiment);
|
|
44
24
|
onClose();
|
|
45
|
-
} catch
|
|
46
|
-
setError(editorCopy.createError);
|
|
25
|
+
} catch (cause) {
|
|
26
|
+
setError(cause instanceof CreateRefused ? cause.message : editorCopy.createError);
|
|
47
27
|
} finally{
|
|
28
|
+
inFlight.current = false;
|
|
48
29
|
setPending(false);
|
|
49
30
|
}
|
|
50
31
|
}
|
|
@@ -72,28 +53,7 @@ export function CreateExperimentDrawer({ onCreate, onClose }) {
|
|
|
72
53
|
/*#__PURE__*/ _jsx("input", {
|
|
73
54
|
value: name,
|
|
74
55
|
disabled: pending,
|
|
75
|
-
onChange: (event)=>
|
|
76
|
-
setName(event.currentTarget.value);
|
|
77
|
-
if (!slugEdited) setSlug(slugify(event.currentTarget.value));
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
]
|
|
81
|
-
}),
|
|
82
|
-
/*#__PURE__*/ _jsxs("label", {
|
|
83
|
-
style: {
|
|
84
|
-
display: 'block',
|
|
85
|
-
marginBottom: 8
|
|
86
|
-
},
|
|
87
|
-
children: [
|
|
88
|
-
editorCopy.slug,
|
|
89
|
-
/*#__PURE__*/ _jsx("input", {
|
|
90
|
-
value: slug,
|
|
91
|
-
maxLength: 64,
|
|
92
|
-
disabled: pending,
|
|
93
|
-
onChange: (event)=>{
|
|
94
|
-
setSlugEdited(true);
|
|
95
|
-
setSlug(event.currentTarget.value);
|
|
96
|
-
}
|
|
56
|
+
onChange: (event)=>setName(event.currentTarget.value)
|
|
97
57
|
})
|
|
98
58
|
]
|
|
99
59
|
}),
|
|
@@ -103,7 +63,7 @@ export function CreateExperimentDrawer({ onCreate, onClose }) {
|
|
|
103
63
|
}) : null,
|
|
104
64
|
/*#__PURE__*/ _jsx("button", {
|
|
105
65
|
type: "button",
|
|
106
|
-
disabled: pending || !name.trim()
|
|
66
|
+
disabled: pending || !name.trim(),
|
|
107
67
|
onClick: ()=>{
|
|
108
68
|
void submitNew();
|
|
109
69
|
},
|
|
@@ -31,6 +31,7 @@ function EditorRender(props) {
|
|
|
31
31
|
const [creating, setCreating] = useState(false);
|
|
32
32
|
// A non-empty variant is only overwritten after an explicit second confirmation.
|
|
33
33
|
const [confirming, setConfirming] = useState(null);
|
|
34
|
+
const pageTitle = usePuckSelector((state)=>typeof state.appState.data.root.props?.title === 'string' ? state.appState.data.root.props.title : '');
|
|
34
35
|
const selected = usePuckSelector((state)=>state.selectedItem?.props.id === props.id);
|
|
35
36
|
const originalEmpty = usePuckSelector((state)=>{
|
|
36
37
|
const content = state.selectedItem?.props.control;
|
|
@@ -138,9 +139,14 @@ function EditorRender(props) {
|
|
|
138
139
|
children: editorCopy.createLabel
|
|
139
140
|
}),
|
|
140
141
|
creating ? /*#__PURE__*/ _jsx(CreateExperimentDrawer, {
|
|
142
|
+
initialName: pageTitle,
|
|
141
143
|
onCreate: bind,
|
|
142
144
|
onClose: ()=>setCreating(false)
|
|
143
145
|
}) : null,
|
|
146
|
+
experiment?.status === 'draft' ? /*#__PURE__*/ _jsx("a", {
|
|
147
|
+
href: `/admin/experiments/${encodeURIComponent(String(experimentId))}`,
|
|
148
|
+
children: editorCopy.setup
|
|
149
|
+
}) : null,
|
|
144
150
|
/*#__PURE__*/ _jsx("div", {
|
|
145
151
|
style: {
|
|
146
152
|
display: 'flex',
|
|
@@ -5,21 +5,22 @@ export declare const editorCopy: {
|
|
|
5
5
|
readonly variantB: "Variant B";
|
|
6
6
|
readonly variantC: "Variant C";
|
|
7
7
|
};
|
|
8
|
-
readonly unbound: "Choose or create
|
|
9
|
-
readonly draft: "This block shows the Original to everyone until the
|
|
8
|
+
readonly unbound: "Choose or create a test to show different versions.";
|
|
9
|
+
readonly draft: "This block shows the Original to everyone until the test starts.";
|
|
10
10
|
readonly running: "Changing this content mid-test mixes two versions in one result";
|
|
11
11
|
readonly decided: (winner: string) => string;
|
|
12
|
-
readonly archived: "This
|
|
12
|
+
readonly archived: "This test is archived; the winning content is shown to everyone.";
|
|
13
13
|
readonly copy: (label: string) => string;
|
|
14
14
|
readonly replaceConfirm: (label: string) => string;
|
|
15
15
|
readonly replace: "Replace";
|
|
16
|
-
readonly createLabel: "
|
|
16
|
+
readonly createLabel: "Test this block";
|
|
17
17
|
readonly creating: "Creating…";
|
|
18
18
|
readonly cancel: "Cancel";
|
|
19
19
|
readonly name: "Name";
|
|
20
|
-
readonly
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
23
|
-
readonly
|
|
24
|
-
readonly
|
|
20
|
+
readonly setup: "Set up the test";
|
|
21
|
+
readonly nameExists: "This name is too close to an existing test. Choose a different name.";
|
|
22
|
+
readonly createError: "Couldn't create the test. Try again.";
|
|
23
|
+
readonly loading: "Loading test…";
|
|
24
|
+
readonly loadError: "Couldn't load this test. Try selecting it again.";
|
|
25
|
+
readonly tabs: "Test versions";
|
|
25
26
|
};
|
package/dist/puck/editor/copy.js
CHANGED
|
@@ -5,21 +5,22 @@ export const editorCopy = {
|
|
|
5
5
|
variantB: 'Variant B',
|
|
6
6
|
variantC: 'Variant C'
|
|
7
7
|
},
|
|
8
|
-
unbound: 'Choose or create
|
|
9
|
-
draft: 'This block shows the Original to everyone until the
|
|
8
|
+
unbound: 'Choose or create a test to show different versions.',
|
|
9
|
+
draft: 'This block shows the Original to everyone until the test starts.',
|
|
10
10
|
running: 'Changing this content mid-test mixes two versions in one result',
|
|
11
|
-
decided: (winner)=>`Decided: ${winner}. Use 'Make it the content' on the
|
|
12
|
-
archived: 'This
|
|
11
|
+
decided: (winner)=>`Decided: ${winner}. Use 'Make it the content' on the test to finish.`,
|
|
12
|
+
archived: 'This test is archived; the winning content is shown to everyone.',
|
|
13
13
|
copy: (label)=>`Copy Original → ${label}`,
|
|
14
14
|
replaceConfirm: (label)=>`${label} already has content. Replace it with a copy of the Original?`,
|
|
15
15
|
replace: 'Replace',
|
|
16
|
-
createLabel: '
|
|
16
|
+
createLabel: 'Test this block',
|
|
17
17
|
creating: 'Creating…',
|
|
18
18
|
cancel: 'Cancel',
|
|
19
19
|
name: 'Name',
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
setup: 'Set up the test',
|
|
21
|
+
nameExists: "This name is too close to an existing test. Choose a different name.",
|
|
22
|
+
createError: "Couldn't create the test. Try again.",
|
|
23
|
+
loading: 'Loading test…',
|
|
24
|
+
loadError: "Couldn't load this test. Try selecting it again.",
|
|
25
|
+
tabs: 'Test versions'
|
|
25
26
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ExperimentRef } from '../../types/puck.js';
|
|
2
|
+
import { newTestBody } from './editor.model.js';
|
|
3
|
+
/** Only a server refusal may supply editor-facing error text. */
|
|
4
|
+
export declare class CreateRefused extends Error {
|
|
5
|
+
}
|
|
6
|
+
/** Shared lookup, complete draft creation and one retry for a racing slug collision. */
|
|
7
|
+
export declare function createTest(apiRoute: string, input: Omit<Parameters<typeof newTestBody>[0], 'slug'>): Promise<ExperimentRef>;
|