@delmaredigital/payload-experiments 0.1.0-rc.7 → 0.2.0-rc.2
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/layers.js +43 -12
- 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/adminRsc.d.ts +3 -0
- package/dist/exports/adminRsc.js +3 -0
- package/dist/exports/index.d.ts +1 -1
- 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 +7 -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
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { formatAdminURL } from 'payload/shared';
|
|
3
|
+
import { notFound, redirect } from 'next/navigation.js';
|
|
4
|
+
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent';
|
|
5
|
+
import { serializeAttributeKeys, serializeFlagPoints, serializeMetricSources } from '../../collections/serialize.js';
|
|
6
|
+
import { puckCollections } from '../../options.js';
|
|
7
|
+
import { xpOptionsFromReq } from '../../options/fromReq.js';
|
|
8
|
+
import { XP_TESTS_SURFACE } from '../paths.js';
|
|
9
|
+
import { SURFACE_COPY } from '../copy/surface.js';
|
|
10
|
+
import { routeAccess } from './access.model.js';
|
|
11
|
+
export async function ExperimentsRootView({ initPageResult, params, searchParams }) {
|
|
12
|
+
const { req } = initPageResult;
|
|
13
|
+
const adminRoute = req.payload.config.routes.admin;
|
|
14
|
+
const segments = (await params)?.segments;
|
|
15
|
+
const path = Array.isArray(segments) ? segments : [];
|
|
16
|
+
const tail = path[0] === 'experiments' ? path.slice(1) : path;
|
|
17
|
+
let route;
|
|
18
|
+
let id;
|
|
19
|
+
if (tail.length === 0) route = 'home';
|
|
20
|
+
else if (tail.length === 1 && tail[0] === 'new') route = 'new';
|
|
21
|
+
else if (tail.length === 1) {
|
|
22
|
+
route = 'test';
|
|
23
|
+
id = tail[0];
|
|
24
|
+
} else if (tail.length === 2 && tail[1] === 'results') {
|
|
25
|
+
route = 'results';
|
|
26
|
+
id = tail[0];
|
|
27
|
+
} else return notFound();
|
|
28
|
+
if (!req.user) {
|
|
29
|
+
const { login } = req.payload.config.admin.routes;
|
|
30
|
+
const query = new URLSearchParams();
|
|
31
|
+
for (const [key, value] of Object.entries(await searchParams ?? {})){
|
|
32
|
+
if (Array.isArray(value)) value.forEach((item)=>query.append(key, item));
|
|
33
|
+
else if (value !== undefined) query.set(key, String(value));
|
|
34
|
+
}
|
|
35
|
+
const currentPath = formatAdminURL({
|
|
36
|
+
adminRoute,
|
|
37
|
+
path: `/experiments${tail.length ? `/${tail.map(encodeURIComponent).join('/')}` : ''}${query.size ? `?${query}` : ''}`
|
|
38
|
+
});
|
|
39
|
+
return redirect(formatAdminURL({
|
|
40
|
+
adminRoute,
|
|
41
|
+
path: `${login}?redirect=${encodeURIComponent(currentPath)}`
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
const options = xpOptionsFromReq(req);
|
|
45
|
+
async function permits(access) {
|
|
46
|
+
try {
|
|
47
|
+
return await access({
|
|
48
|
+
req
|
|
49
|
+
}) === true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const canManage = await permits(options.access.manage);
|
|
55
|
+
const isAdmin = options.access.admin ? await permits(options.access.admin) : false;
|
|
56
|
+
const canReadResults = options.access.readResults ? await permits(options.access.readResults) : canManage;
|
|
57
|
+
if (routeAccess({
|
|
58
|
+
route,
|
|
59
|
+
hasUser: true,
|
|
60
|
+
canManage,
|
|
61
|
+
canReadResults
|
|
62
|
+
}) !== 'ok') {
|
|
63
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
64
|
+
style: {
|
|
65
|
+
padding: 'var(--base)',
|
|
66
|
+
color: 'var(--theme-text)',
|
|
67
|
+
background: 'var(--theme-bg)'
|
|
68
|
+
},
|
|
69
|
+
children: SURFACE_COPY.noAccess
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const flagPoints = serializeFlagPoints(options);
|
|
73
|
+
// xp-state is manager-readable only. Results-only readers do not edit settings.
|
|
74
|
+
if (canManage && flagPoints.length) {
|
|
75
|
+
const state = await req.payload.findGlobal({
|
|
76
|
+
slug: 'xp-state',
|
|
77
|
+
req,
|
|
78
|
+
overrideAccess: false,
|
|
79
|
+
depth: 0
|
|
80
|
+
});
|
|
81
|
+
const defaults = state.flagDefaults;
|
|
82
|
+
for (const point of flagPoints)point.effectiveDefaults = defaults?.[point.id]?.params ?? point.defaultParams;
|
|
83
|
+
}
|
|
84
|
+
const registrations = puckCollections(options);
|
|
85
|
+
const pages = route === 'new' && canManage && options.puck?.enabled && registrations.length ? (await Promise.all(registrations.map(async ({ slug: collection })=>{
|
|
86
|
+
try {
|
|
87
|
+
const config = req.payload.config.collections.find((item)=>item.slug === collection);
|
|
88
|
+
const useAsTitle = config?.admin.useAsTitle ?? 'id';
|
|
89
|
+
const label = config?.labels.plural;
|
|
90
|
+
// Keep /rsc dependency-safe: translations is not a declared runtime dependency,
|
|
91
|
+
// so a per-language label picks the request language, then English, then any string.
|
|
92
|
+
const byLanguage = label && typeof label === 'object' ? label : {};
|
|
93
|
+
const collectionLabel = typeof label === 'string' ? label : [
|
|
94
|
+
byLanguage[req.i18n?.language ?? 'en'],
|
|
95
|
+
byLanguage.en,
|
|
96
|
+
...Object.values(byLanguage)
|
|
97
|
+
].find((value)=>typeof value === 'string') ?? collection;
|
|
98
|
+
const result = await req.payload.find({
|
|
99
|
+
collection,
|
|
100
|
+
req,
|
|
101
|
+
overrideAccess: false,
|
|
102
|
+
depth: 0,
|
|
103
|
+
limit: 50,
|
|
104
|
+
sort: '-updatedAt',
|
|
105
|
+
draft: true,
|
|
106
|
+
select: {
|
|
107
|
+
[useAsTitle]: true
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return result.docs.map((doc)=>{
|
|
111
|
+
const id = doc.id;
|
|
112
|
+
const customUrl = options.admin?.editorUrl?.({
|
|
113
|
+
collection,
|
|
114
|
+
id,
|
|
115
|
+
draft: true
|
|
116
|
+
});
|
|
117
|
+
const title = doc[useAsTitle];
|
|
118
|
+
return {
|
|
119
|
+
collection,
|
|
120
|
+
collectionLabel,
|
|
121
|
+
id,
|
|
122
|
+
title: typeof title === 'string' || typeof title === 'number' ? String(title) : String(id),
|
|
123
|
+
editorUrl: typeof customUrl === 'string' ? customUrl : formatAdminURL({
|
|
124
|
+
adminRoute,
|
|
125
|
+
path: `/collections/${collection}/${encodeURIComponent(String(id))}`
|
|
126
|
+
})
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}))).flat() : undefined;
|
|
133
|
+
const descriptions = options.admin?.descriptions;
|
|
134
|
+
const clientProps = {
|
|
135
|
+
route,
|
|
136
|
+
...id === undefined ? {} : {
|
|
137
|
+
id
|
|
138
|
+
},
|
|
139
|
+
adminRoute,
|
|
140
|
+
apiRoute: req.payload.config.routes.api,
|
|
141
|
+
canManage,
|
|
142
|
+
canReadResults,
|
|
143
|
+
isAdmin,
|
|
144
|
+
flagPoints,
|
|
145
|
+
...pages === undefined ? {} : {
|
|
146
|
+
pages
|
|
147
|
+
},
|
|
148
|
+
attributeKeys: serializeAttributeKeys(options),
|
|
149
|
+
metricSources: serializeMetricSources(options),
|
|
150
|
+
...descriptions ? {
|
|
151
|
+
admin: {
|
|
152
|
+
descriptions: {
|
|
153
|
+
...descriptions
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} : {}
|
|
157
|
+
};
|
|
158
|
+
return RenderServerComponent({
|
|
159
|
+
Component: XP_TESTS_SURFACE,
|
|
160
|
+
Fallback: TestsSurfaceFallback,
|
|
161
|
+
importMap: req.payload.importMap,
|
|
162
|
+
clientProps
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function TestsSurfaceFallback() {
|
|
166
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
167
|
+
role: "alert",
|
|
168
|
+
style: {
|
|
169
|
+
padding: 'var(--base)',
|
|
170
|
+
color: 'var(--theme-text)',
|
|
171
|
+
background: 'var(--theme-bg)'
|
|
172
|
+
},
|
|
173
|
+
children: SURFACE_COPY.notInstalled
|
|
174
|
+
});
|
|
175
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TestsRoute } from '../../types/surface.js';
|
|
2
|
+
export declare function routeAccess({ route, hasUser, canManage, canReadResults }: {
|
|
3
|
+
route: TestsRoute;
|
|
4
|
+
hasUser: boolean;
|
|
5
|
+
canManage: unknown;
|
|
6
|
+
canReadResults?: unknown;
|
|
7
|
+
}): 'login' | 'unauthorized' | 'ok';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as paths from '../../admin/paths.js';
|
|
2
|
-
import { TAB_DESCRIPTIONS } from '../../admin/copy/tabs.js';
|
|
3
2
|
import { managedAccess } from '../access.js';
|
|
4
3
|
import { serializeAttributeKeys, serializeFlagPoints } from '../serialize.js';
|
|
5
4
|
import { TARGETING_OPS, isOpValid } from '../../targeting/ops.js';
|
|
@@ -9,17 +8,6 @@ import { lockHook } from './lockHook.js';
|
|
|
9
8
|
import { deleteGuard, deleteGuardAfter } from './deleteGuard.js';
|
|
10
9
|
import { checkArms } from './armInvariants.js';
|
|
11
10
|
import { XpNotImplemented } from '../../errors.js';
|
|
12
|
-
const locked = (field, admin = {})=>({
|
|
13
|
-
...admin,
|
|
14
|
-
custom: {
|
|
15
|
-
...admin.custom,
|
|
16
|
-
field
|
|
17
|
-
},
|
|
18
|
-
components: {
|
|
19
|
-
...admin.components,
|
|
20
|
-
Description: paths.XP_LOCKED_NOTICE
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
11
|
const positiveInteger = (value)=>value != null && Number.isInteger(value) && value > 0 || 'Enter a positive whole number.';
|
|
24
12
|
export function experimentsCollection(options) {
|
|
25
13
|
const flagPoints = serializeFlagPoints(options);
|
|
@@ -43,7 +31,8 @@ export function experimentsCollection(options) {
|
|
|
43
31
|
graphQL: false,
|
|
44
32
|
disableDuplicate: true,
|
|
45
33
|
versions: {
|
|
46
|
-
drafts: false
|
|
34
|
+
drafts: false,
|
|
35
|
+
maxPerDoc: 50
|
|
47
36
|
},
|
|
48
37
|
access: {
|
|
49
38
|
...managedAccess(options),
|
|
@@ -59,19 +48,26 @@ export function experimentsCollection(options) {
|
|
|
59
48
|
} : false
|
|
60
49
|
},
|
|
61
50
|
admin: {
|
|
51
|
+
// group:false hides navigation while keeping custom view routes reachable (hidden causes 404s).
|
|
52
|
+
group: false,
|
|
62
53
|
useAsTitle: 'name',
|
|
63
54
|
defaultColumns: [
|
|
64
55
|
'name',
|
|
65
56
|
'status',
|
|
66
57
|
'kind',
|
|
67
|
-
'flagPoint'
|
|
68
|
-
'listSummary'
|
|
58
|
+
'flagPoint'
|
|
69
59
|
],
|
|
70
60
|
components: {
|
|
71
61
|
views: {
|
|
62
|
+
list: {
|
|
63
|
+
Component: paths.XP_TESTS_LIST_REDIRECT
|
|
64
|
+
},
|
|
72
65
|
edit: {
|
|
66
|
+
default: {
|
|
67
|
+
Component: paths.XP_TEST_EDIT_REDIRECT
|
|
68
|
+
},
|
|
73
69
|
results: {
|
|
74
|
-
Component: paths.
|
|
70
|
+
Component: paths.XP_TEST_EDIT_REDIRECT,
|
|
75
71
|
path: '/results',
|
|
76
72
|
tab: {
|
|
77
73
|
label: 'Results',
|
|
@@ -98,16 +94,6 @@ export function experimentsCollection(options) {
|
|
|
98
94
|
]
|
|
99
95
|
},
|
|
100
96
|
fields: [
|
|
101
|
-
{
|
|
102
|
-
name: 'actions',
|
|
103
|
-
type: 'ui',
|
|
104
|
-
admin: {
|
|
105
|
-
position: 'sidebar',
|
|
106
|
-
components: {
|
|
107
|
-
Field: paths.XP_ACTIONS
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
97
|
{
|
|
112
98
|
name: 'status',
|
|
113
99
|
type: 'select',
|
|
@@ -122,30 +108,7 @@ export function experimentsCollection(options) {
|
|
|
122
108
|
],
|
|
123
109
|
admin: {
|
|
124
110
|
position: 'sidebar',
|
|
125
|
-
readOnly: true
|
|
126
|
-
components: {
|
|
127
|
-
Cell: paths.XP_STATUS_CELL
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: 'listSummary',
|
|
133
|
-
type: 'ui',
|
|
134
|
-
admin: {
|
|
135
|
-
condition: ()=>false,
|
|
136
|
-
components: {
|
|
137
|
-
Cell: paths.XP_SUMMARY_CELL
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
name: 'phaseHistory',
|
|
143
|
-
type: 'ui',
|
|
144
|
-
admin: {
|
|
145
|
-
position: 'sidebar',
|
|
146
|
-
components: {
|
|
147
|
-
Field: paths.XP_PHASE_HISTORY
|
|
148
|
-
}
|
|
111
|
+
readOnly: true
|
|
149
112
|
}
|
|
150
113
|
},
|
|
151
114
|
{
|
|
@@ -153,13 +116,11 @@ export function experimentsCollection(options) {
|
|
|
153
116
|
tabs: [
|
|
154
117
|
{
|
|
155
118
|
label: 'What',
|
|
156
|
-
description: TAB_DESCRIPTIONS.What,
|
|
157
119
|
fields: [
|
|
158
120
|
{
|
|
159
121
|
name: 'name',
|
|
160
122
|
type: 'text',
|
|
161
|
-
required: true
|
|
162
|
-
admin: locked('name')
|
|
123
|
+
required: true
|
|
163
124
|
},
|
|
164
125
|
{
|
|
165
126
|
name: 'slug',
|
|
@@ -169,8 +130,7 @@ export function experimentsCollection(options) {
|
|
|
169
130
|
},
|
|
170
131
|
{
|
|
171
132
|
name: 'hypothesis',
|
|
172
|
-
type: 'textarea'
|
|
173
|
-
admin: locked('hypothesis')
|
|
133
|
+
type: 'textarea'
|
|
174
134
|
},
|
|
175
135
|
{
|
|
176
136
|
name: 'kind',
|
|
@@ -189,14 +149,6 @@ export function experimentsCollection(options) {
|
|
|
189
149
|
{
|
|
190
150
|
name: 'flagPoint',
|
|
191
151
|
type: 'text',
|
|
192
|
-
admin: locked('flagPoint', {
|
|
193
|
-
components: {
|
|
194
|
-
Field: paths.XP_FLAG_POINT_SELECT
|
|
195
|
-
},
|
|
196
|
-
custom: {
|
|
197
|
-
flagPoints
|
|
198
|
-
}
|
|
199
|
-
}),
|
|
200
152
|
validate: (value, { data })=>!value && data?.kind !== 'flag' || flagPoints.some((point)=>point.id === value) || 'Choose a registered flag point.'
|
|
201
153
|
},
|
|
202
154
|
{
|
|
@@ -209,7 +161,6 @@ export function experimentsCollection(options) {
|
|
|
209
161
|
'pageview',
|
|
210
162
|
'manual'
|
|
211
163
|
],
|
|
212
|
-
admin: locked('exposurePolicy'),
|
|
213
164
|
validate: (value, { data })=>{
|
|
214
165
|
const allowed = data?.kind === 'block' ? [
|
|
215
166
|
'render',
|
|
@@ -226,7 +177,6 @@ export function experimentsCollection(options) {
|
|
|
226
177
|
},
|
|
227
178
|
{
|
|
228
179
|
label: 'Who',
|
|
229
|
-
description: TAB_DESCRIPTIONS.Who,
|
|
230
180
|
fields: [
|
|
231
181
|
{
|
|
232
182
|
name: 'unitType',
|
|
@@ -246,12 +196,7 @@ export function experimentsCollection(options) {
|
|
|
246
196
|
defaultValue: 100,
|
|
247
197
|
min: 1,
|
|
248
198
|
max: 100,
|
|
249
|
-
validate: (value)=>positiveInteger(value) === true && value <= 100 || 'Enter a whole percentage from 1 to 100.'
|
|
250
|
-
admin: locked('allocation', {
|
|
251
|
-
components: {
|
|
252
|
-
Field: paths.XP_ALLOCATION_FIELD
|
|
253
|
-
}
|
|
254
|
-
})
|
|
199
|
+
validate: (value)=>positiveInteger(value) === true && value <= 100 || 'Enter a whole percentage from 1 to 100.'
|
|
255
200
|
},
|
|
256
201
|
{
|
|
257
202
|
name: 'layer',
|
|
@@ -261,21 +206,12 @@ export function experimentsCollection(options) {
|
|
|
261
206
|
{
|
|
262
207
|
name: 'targeting',
|
|
263
208
|
type: 'array',
|
|
264
|
-
admin: locked('targeting'),
|
|
265
209
|
fields: [
|
|
266
210
|
{
|
|
267
211
|
name: 'attribute',
|
|
268
212
|
type: 'text',
|
|
269
213
|
required: true,
|
|
270
|
-
validate: (value)=>typeof value === 'string' && Object.hasOwn(attributeKeys, value) || 'Choose a registered attribute.'
|
|
271
|
-
admin: {
|
|
272
|
-
components: {
|
|
273
|
-
Field: paths.XP_TARGETING_ATTRIBUTE
|
|
274
|
-
},
|
|
275
|
-
custom: {
|
|
276
|
-
attributeKeys
|
|
277
|
-
}
|
|
278
|
-
}
|
|
214
|
+
validate: (value)=>typeof value === 'string' && Object.hasOwn(attributeKeys, value) || 'Choose a registered attribute.'
|
|
279
215
|
},
|
|
280
216
|
{
|
|
281
217
|
name: 'op',
|
|
@@ -284,15 +220,7 @@ export function experimentsCollection(options) {
|
|
|
284
220
|
options: [
|
|
285
221
|
...new Set(Object.values(TARGETING_OPS).flat())
|
|
286
222
|
],
|
|
287
|
-
validate: (value, { siblingData })=>isOpValid(attributeKeys[siblingData?.attribute ?? '']?.type ?? '', value) || 'Choose an operator supported by this attribute.'
|
|
288
|
-
admin: {
|
|
289
|
-
components: {
|
|
290
|
-
Field: paths.XP_TARGETING_OP
|
|
291
|
-
},
|
|
292
|
-
custom: {
|
|
293
|
-
attributeKeys
|
|
294
|
-
}
|
|
295
|
-
}
|
|
223
|
+
validate: (value, { siblingData })=>isOpValid(attributeKeys[siblingData?.attribute ?? '']?.type ?? '', value) || 'Choose an operator supported by this attribute.'
|
|
296
224
|
},
|
|
297
225
|
{
|
|
298
226
|
name: 'value',
|
|
@@ -305,14 +233,6 @@ export function experimentsCollection(options) {
|
|
|
305
233
|
const valid = type === 'string' && (op === 'in' || op === 'nin') ? Array.isArray(value) && value.length > 0 && value.every((item)=>typeof item === 'string') : typeof value === type && (type !== 'number' || Number.isFinite(value));
|
|
306
234
|
if (!valid && type === 'string' && (op === 'in' || op === 'nin') && Array.isArray(value) && value.length === 0) return 'Add at least one value; an empty list matches nobody.';
|
|
307
235
|
return valid || 'Enter a value of the type required by this attribute and operator.';
|
|
308
|
-
},
|
|
309
|
-
admin: {
|
|
310
|
-
components: {
|
|
311
|
-
Field: paths.XP_TARGETING_VALUE
|
|
312
|
-
},
|
|
313
|
-
custom: {
|
|
314
|
-
attributeKeys
|
|
315
|
-
}
|
|
316
236
|
}
|
|
317
237
|
}
|
|
318
238
|
]
|
|
@@ -321,7 +241,6 @@ export function experimentsCollection(options) {
|
|
|
321
241
|
},
|
|
322
242
|
{
|
|
323
243
|
label: 'Groups',
|
|
324
|
-
description: TAB_DESCRIPTIONS.Groups,
|
|
325
244
|
fields: [
|
|
326
245
|
{
|
|
327
246
|
name: 'arms',
|
|
@@ -337,14 +256,6 @@ export function experimentsCollection(options) {
|
|
|
337
256
|
throw error;
|
|
338
257
|
}
|
|
339
258
|
},
|
|
340
|
-
admin: locked('arms', {
|
|
341
|
-
components: {
|
|
342
|
-
Field: paths.XP_ARMS_FIELD
|
|
343
|
-
},
|
|
344
|
-
custom: {
|
|
345
|
-
flagPoints
|
|
346
|
-
}
|
|
347
|
-
}),
|
|
348
259
|
fields: [
|
|
349
260
|
{
|
|
350
261
|
name: 'key',
|
|
@@ -405,12 +316,10 @@ export function experimentsCollection(options) {
|
|
|
405
316
|
},
|
|
406
317
|
{
|
|
407
318
|
label: 'Metrics',
|
|
408
|
-
description: TAB_DESCRIPTIONS.Metrics,
|
|
409
319
|
fields: [
|
|
410
320
|
{
|
|
411
321
|
name: 'metrics',
|
|
412
322
|
type: 'group',
|
|
413
|
-
admin: locked('metrics'),
|
|
414
323
|
fields: [
|
|
415
324
|
metric('primary'),
|
|
416
325
|
metric('secondary', true),
|
|
@@ -420,7 +329,6 @@ export function experimentsCollection(options) {
|
|
|
420
329
|
{
|
|
421
330
|
name: 'analysis',
|
|
422
331
|
type: 'group',
|
|
423
|
-
admin: locked('analysis'),
|
|
424
332
|
fields: [
|
|
425
333
|
{
|
|
426
334
|
name: 'method',
|
|
@@ -475,21 +383,6 @@ export function experimentsCollection(options) {
|
|
|
475
383
|
]
|
|
476
384
|
}
|
|
477
385
|
]
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
label: 'Review',
|
|
481
|
-
description: TAB_DESCRIPTIONS.Review,
|
|
482
|
-
fields: [
|
|
483
|
-
{
|
|
484
|
-
name: 'review',
|
|
485
|
-
type: 'ui',
|
|
486
|
-
admin: {
|
|
487
|
-
components: {
|
|
488
|
-
Field: paths.XP_REVIEW_PANEL
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
]
|
|
493
386
|
}
|
|
494
387
|
]
|
|
495
388
|
},
|
|
@@ -3,5 +3,11 @@ export const createDefaults = ({ data, operation })=>{
|
|
|
3
3
|
if (operation === 'create' && data && (!data.arms || Array.isArray(data.arms) && data.arms.length === 0)) {
|
|
4
4
|
data.arms = defaultArms(data.kind);
|
|
5
5
|
}
|
|
6
|
+
if (operation === 'create' && data && data.analysis?.plannedDurationDays == null) {
|
|
7
|
+
data.analysis = {
|
|
8
|
+
...data.analysis,
|
|
9
|
+
plannedDurationDays: 28
|
|
10
|
+
};
|
|
11
|
+
}
|
|
6
12
|
return data;
|
|
7
13
|
};
|
|
@@ -2,57 +2,88 @@ import { XpRefusal } from '../errors.js';
|
|
|
2
2
|
import { XP_LAYER_USAGE } from '../admin/paths.js';
|
|
3
3
|
import { managedAccess } from './access.js';
|
|
4
4
|
export function layersCollection(options) {
|
|
5
|
+
const usageAdmin = {
|
|
6
|
+
description: 'See which tests use this group and how much room is left.',
|
|
7
|
+
components: {
|
|
8
|
+
Field: XP_LAYER_USAGE
|
|
9
|
+
}
|
|
10
|
+
};
|
|
5
11
|
return {
|
|
6
12
|
slug: 'xp-layers',
|
|
7
13
|
graphQL: false,
|
|
8
14
|
disableDuplicate: true,
|
|
15
|
+
// group:false hides navigation while keeping collection routes reachable (hidden causes 404s).
|
|
16
|
+
labels: {
|
|
17
|
+
singular: 'Test group',
|
|
18
|
+
plural: 'Test groups (advanced)'
|
|
19
|
+
},
|
|
9
20
|
admin: {
|
|
21
|
+
group: false,
|
|
10
22
|
useAsTitle: 'name'
|
|
11
23
|
},
|
|
12
24
|
access: managedAccess(options),
|
|
13
25
|
fields: [
|
|
14
26
|
{
|
|
15
27
|
name: 'name',
|
|
28
|
+
label: 'Name',
|
|
16
29
|
type: 'text',
|
|
17
|
-
required: true
|
|
30
|
+
required: true,
|
|
31
|
+
admin: {
|
|
32
|
+
description: 'Give this test group a name people will recognize.'
|
|
33
|
+
}
|
|
18
34
|
},
|
|
19
35
|
{
|
|
20
36
|
name: 'slug',
|
|
37
|
+
label: 'Short name (used in code)',
|
|
21
38
|
type: 'text',
|
|
22
39
|
unique: true,
|
|
23
40
|
required: true,
|
|
41
|
+
admin: {
|
|
42
|
+
description: 'Use a unique short name for code that refers to this group.'
|
|
43
|
+
},
|
|
24
44
|
validate: (value, { operation, previousValue })=>{
|
|
25
45
|
if (operation === 'update' && value === undefined) return true;
|
|
26
|
-
if (typeof value !== 'string' || !value.length) return 'Enter a
|
|
27
|
-
return operation !== 'update' || value === previousValue || '
|
|
46
|
+
if (typeof value !== 'string' || !value.length) return 'Enter a short name.';
|
|
47
|
+
return operation !== 'update' || value === previousValue || "The short name can't change after the group is created.";
|
|
28
48
|
}
|
|
29
49
|
},
|
|
30
50
|
{
|
|
31
51
|
name: 'unitType',
|
|
52
|
+
label: 'Counts',
|
|
32
53
|
type: 'select',
|
|
33
54
|
required: true,
|
|
34
55
|
options: [
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
{
|
|
57
|
+
label: 'Visitors',
|
|
58
|
+
value: 'visitor'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: 'Signed-in users',
|
|
62
|
+
value: 'user'
|
|
63
|
+
}
|
|
37
64
|
],
|
|
65
|
+
admin: {
|
|
66
|
+
description: 'Choose whether tests in this group count visitors or signed-in users.'
|
|
67
|
+
},
|
|
38
68
|
validate: (value, { operation, previousValue })=>{
|
|
39
69
|
if (operation === 'update' && value === undefined) return true;
|
|
40
70
|
if (value !== 'visitor' && value !== 'user') return 'Choose visitors or signed-in users.';
|
|
41
|
-
return operation !== 'update' || value === previousValue || '
|
|
71
|
+
return operation !== 'update' || value === previousValue || "Who it counts can't change after the group is created.";
|
|
42
72
|
}
|
|
43
73
|
},
|
|
44
74
|
{
|
|
45
75
|
name: 'description',
|
|
46
|
-
|
|
76
|
+
label: 'Notes',
|
|
77
|
+
type: 'textarea',
|
|
78
|
+
admin: {
|
|
79
|
+
description: 'Explain when people should use this test group.'
|
|
80
|
+
}
|
|
47
81
|
},
|
|
48
82
|
{
|
|
49
83
|
name: 'usage',
|
|
84
|
+
label: 'Tests in this group',
|
|
50
85
|
type: 'ui',
|
|
51
|
-
admin:
|
|
52
|
-
components: {
|
|
53
|
-
Field: XP_LAYER_USAGE
|
|
54
|
-
}
|
|
55
|
-
}
|
|
86
|
+
admin: usageAdmin
|
|
56
87
|
}
|
|
57
88
|
],
|
|
58
89
|
hooks: {
|