@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.
Files changed (207) hide show
  1. package/README.md +28 -0
  2. package/dist/admin/LayerUsage.d.ts +1 -1
  3. package/dist/admin/LayerUsage.js +23 -11
  4. package/dist/admin/MetricSourceSelect.js +2 -1
  5. package/dist/admin/TestsNavLink.d.ts +1 -0
  6. package/dist/admin/TestsNavLink.js +30 -0
  7. package/dist/admin/allocation.model.js +1 -1
  8. package/dist/admin/copy/actions.d.ts +0 -2
  9. package/dist/admin/copy/actions.js +0 -2
  10. package/dist/admin/copy/advanced.d.ts +55 -0
  11. package/dist/admin/copy/advanced.js +55 -0
  12. package/dist/admin/copy/armSelect.js +1 -1
  13. package/dist/admin/copy/arms.d.ts +0 -13
  14. package/dist/admin/copy/arms.js +0 -13
  15. package/dist/admin/copy/{who.d.ts → audience.d.ts} +1 -9
  16. package/dist/admin/copy/{who.js → audience.js} +1 -9
  17. package/dist/admin/copy/history.d.ts +0 -4
  18. package/dist/admin/copy/history.js +3 -7
  19. package/dist/admin/copy/metrics.d.ts +2 -2
  20. package/dist/admin/copy/metrics.js +2 -2
  21. package/dist/admin/copy/newTest.d.ts +32 -0
  22. package/dist/admin/copy/newTest.js +32 -0
  23. package/dist/admin/copy/readiness.d.ts +45 -0
  24. package/dist/admin/copy/readiness.js +50 -0
  25. package/dist/admin/copy/recommendation.d.ts +26 -0
  26. package/dist/admin/copy/recommendation.js +25 -0
  27. package/dist/admin/copy/results.d.ts +0 -1
  28. package/dist/admin/copy/results.js +0 -1
  29. package/dist/admin/copy/review.d.ts +0 -17
  30. package/dist/admin/copy/review.js +1 -112
  31. package/dist/admin/copy/setting.d.ts +10 -0
  32. package/dist/admin/copy/setting.js +10 -0
  33. package/dist/admin/copy/surface.d.ts +36 -0
  34. package/dist/admin/copy/surface.js +47 -0
  35. package/dist/admin/copy/testPage.d.ts +88 -0
  36. package/dist/admin/copy/testPage.js +88 -0
  37. package/dist/admin/paths.d.ts +5 -13
  38. package/dist/admin/paths.js +5 -13
  39. package/dist/admin/review/review.model.d.ts +3 -26
  40. package/dist/admin/review/review.model.js +3 -30
  41. package/dist/admin/surface/ResultsRoute.d.ts +7 -0
  42. package/dist/admin/surface/ResultsRoute.js +58 -0
  43. package/dist/admin/surface/SurfaceShell.d.ts +6 -0
  44. package/dist/admin/surface/SurfaceShell.js +35 -0
  45. package/dist/admin/surface/TestsHome.d.ts +4 -0
  46. package/dist/admin/surface/TestsHome.js +212 -0
  47. package/dist/admin/surface/TestsSurface.d.ts +2 -0
  48. package/dist/admin/surface/TestsSurface.js +36 -0
  49. package/dist/admin/surface/contracts.d.ts +48 -0
  50. package/dist/admin/surface/contracts.js +8 -0
  51. package/dist/admin/surface/home.model.d.ts +41 -0
  52. package/dist/admin/surface/home.model.js +59 -0
  53. package/dist/admin/surface/newTest/NewTest.d.ts +7 -0
  54. package/dist/admin/surface/newTest/NewTest.js +355 -0
  55. package/dist/admin/surface/newTest/newTest.css +9 -0
  56. package/dist/admin/surface/pageBlock/pageBlock.model.d.ts +6 -0
  57. package/dist/admin/surface/pageBlock/pageBlock.model.js +8 -0
  58. package/dist/admin/surface/pageBlock/parts.d.ts +2 -0
  59. package/dist/admin/surface/pageBlock/parts.js +64 -0
  60. package/dist/admin/surface/readiness/ActionDialogs.d.ts +15 -0
  61. package/dist/admin/surface/readiness/ActionDialogs.js +274 -0
  62. package/dist/admin/surface/readiness/Banner.d.ts +2 -0
  63. package/dist/admin/surface/readiness/Banner.js +138 -0
  64. package/dist/admin/surface/readiness/SaveStatus.d.ts +4 -0
  65. package/dist/admin/surface/readiness/SaveStatus.js +50 -0
  66. package/dist/admin/surface/readiness/parts.d.ts +2 -0
  67. package/dist/admin/surface/readiness/parts.js +9 -0
  68. package/dist/admin/surface/readiness/readiness.model.d.ts +22 -0
  69. package/dist/admin/surface/readiness/readiness.model.js +107 -0
  70. package/dist/admin/surface/readiness/useTransition.d.ts +10 -0
  71. package/dist/admin/surface/readiness/useTransition.js +34 -0
  72. package/dist/admin/surface/results/ResultsBody.d.ts +7 -0
  73. package/dist/admin/surface/results/ResultsBody.js +262 -0
  74. package/dist/admin/surface/results/recommendation.model.d.ts +12 -0
  75. package/dist/admin/surface/results/recommendation.model.js +59 -0
  76. package/dist/admin/surface/results/useResults.d.ts +56 -0
  77. package/dist/admin/surface/results/useResults.js +183 -0
  78. package/dist/admin/surface/setting/parts.d.ts +3 -0
  79. package/dist/admin/surface/setting/parts.js +263 -0
  80. package/dist/admin/surface/setting/setting.css +29 -0
  81. package/dist/admin/surface/setting/setting.model.d.ts +8 -0
  82. package/dist/admin/surface/setting/setting.model.js +25 -0
  83. package/dist/admin/surface/surface.css +50 -0
  84. package/dist/admin/surface/test/AdvancedSection.d.ts +2 -0
  85. package/dist/admin/surface/test/AdvancedSection.js +153 -0
  86. package/dist/admin/surface/test/GoalSection.d.ts +2 -0
  87. package/dist/admin/surface/test/GoalSection.js +198 -0
  88. package/dist/admin/surface/test/LengthSection.d.ts +2 -0
  89. package/dist/admin/surface/test/LengthSection.js +87 -0
  90. package/dist/admin/surface/test/Section.d.ts +7 -0
  91. package/dist/admin/surface/test/Section.js +27 -0
  92. package/dist/admin/surface/test/TargetingSection.d.ts +2 -0
  93. package/dist/admin/surface/test/TargetingSection.js +164 -0
  94. package/dist/admin/surface/test/TestHeader.d.ts +4 -0
  95. package/dist/admin/surface/test/TestHeader.js +69 -0
  96. package/dist/admin/surface/test/TestPage.d.ts +7 -0
  97. package/dist/admin/surface/test/TestPage.js +84 -0
  98. package/dist/admin/surface/test/VersionsSection.d.ts +4 -0
  99. package/dist/admin/surface/test/VersionsSection.js +208 -0
  100. package/dist/admin/surface/test/fields.d.ts +55 -0
  101. package/dist/admin/surface/test/fields.js +185 -0
  102. package/dist/admin/surface/test/pageParts.d.ts +2 -0
  103. package/dist/admin/surface/test/pageParts.js +39 -0
  104. package/dist/admin/surface/test/testPage.css +23 -0
  105. package/dist/admin/surface/test/testPage.model.d.ts +17 -0
  106. package/dist/admin/surface/test/testPage.model.js +53 -0
  107. package/dist/admin/surface/testDocument.model.d.ts +39 -0
  108. package/dist/admin/surface/testDocument.model.js +61 -0
  109. package/dist/admin/surface/useTestDocument.d.ts +22 -0
  110. package/dist/admin/surface/useTestDocument.js +345 -0
  111. package/dist/admin/targeting/targeting.model.js +2 -2
  112. package/dist/admin/useLayerUsage.d.ts +0 -5
  113. package/dist/admin/useLayerUsage.js +1 -39
  114. package/dist/admin/views/CollectionEditRedirect.d.ts +4 -0
  115. package/dist/admin/views/CollectionEditRedirect.js +14 -0
  116. package/dist/admin/views/CollectionListRedirect.d.ts +2 -0
  117. package/dist/admin/views/CollectionListRedirect.js +8 -0
  118. package/dist/admin/views/ExperimentsRootView.d.ts +2 -0
  119. package/dist/admin/views/ExperimentsRootView.js +175 -0
  120. package/dist/admin/views/access.model.d.ts +7 -0
  121. package/dist/admin/views/access.model.js +5 -0
  122. package/dist/collections/experiments/config.js +18 -125
  123. package/dist/collections/experiments/createDefaults.js +6 -0
  124. package/dist/collections/layers.js +43 -12
  125. package/dist/collections/metrics.js +70 -14
  126. package/dist/collections/serialize.js +4 -1
  127. package/dist/db/snapshots.d.ts +2 -1
  128. package/dist/db/snapshots.js +2 -2
  129. package/dist/endpoints/results.js +7 -0
  130. package/dist/endpoints/summary.d.ts +1 -1
  131. package/dist/endpoints/summary.js +37 -1
  132. package/dist/exports/adminClient.d.ts +2 -13
  133. package/dist/exports/adminClient.js +2 -13
  134. package/dist/exports/adminRsc.d.ts +3 -0
  135. package/dist/exports/adminRsc.js +3 -0
  136. package/dist/exports/index.d.ts +1 -1
  137. package/dist/options.js +29 -0
  138. package/dist/plugin.js +25 -0
  139. package/dist/puck/editor/CreateExperimentDrawer.d.ts +2 -1
  140. package/dist/puck/editor/CreateExperimentDrawer.js +17 -57
  141. package/dist/puck/editor/ExperimentSlotEditor.js +6 -0
  142. package/dist/puck/editor/copy.d.ts +10 -9
  143. package/dist/puck/editor/copy.js +11 -10
  144. package/dist/puck/editor/createTest.d.ts +7 -0
  145. package/dist/puck/editor/createTest.js +50 -0
  146. package/dist/puck/editor/editor.model.d.ts +28 -1
  147. package/dist/puck/editor/editor.model.js +61 -0
  148. package/dist/results/analyse.js +4 -1
  149. package/dist/results/verdict.d.ts +1 -0
  150. package/dist/results/verdict.js +7 -4
  151. package/dist/transitions/checkStart.d.ts +1 -0
  152. package/dist/transitions/placements.d.ts +5 -1
  153. package/dist/transitions/placements.js +14 -7
  154. package/dist/transitions/preflightData.d.ts +7 -1
  155. package/dist/transitions/preflightData.js +199 -148
  156. package/dist/transitions/start.js +2 -2
  157. package/dist/types/admin.d.ts +1 -0
  158. package/dist/types/contracts.d.ts +17 -0
  159. package/dist/types/endpoints.d.ts +11 -1
  160. package/dist/types/snapshot.d.ts +1 -0
  161. package/dist/types/surface.d.ts +29 -0
  162. package/dist/types/surface.js +1 -0
  163. package/package.json +7 -1
  164. package/dist/access.d.ts +0 -1
  165. package/dist/access.js +0 -2
  166. package/dist/admin/AllocationField.d.ts +0 -2
  167. package/dist/admin/AllocationField.js +0 -80
  168. package/dist/admin/FlagPointSelect.d.ts +0 -2
  169. package/dist/admin/FlagPointSelect.js +0 -54
  170. package/dist/admin/LockedFieldNotice.d.ts +0 -2
  171. package/dist/admin/LockedFieldNotice.js +0 -26
  172. package/dist/admin/PhaseHistory.d.ts +0 -2
  173. package/dist/admin/PhaseHistory.js +0 -76
  174. package/dist/admin/StatusCell.d.ts +0 -3
  175. package/dist/admin/StatusCell.js +0 -19
  176. package/dist/admin/SummaryCell.d.ts +0 -2
  177. package/dist/admin/SummaryCell.js +0 -32
  178. package/dist/admin/actions/AddMetricDialog.d.ts +0 -6
  179. package/dist/admin/actions/AddMetricDialog.js +0 -138
  180. package/dist/admin/actions/AllocateDialog.d.ts +0 -6
  181. package/dist/admin/actions/AllocateDialog.js +0 -81
  182. package/dist/admin/actions/ExperimentActions.d.ts +0 -3
  183. package/dist/admin/actions/ExperimentActions.js +0 -110
  184. package/dist/admin/arms/ArmsField.d.ts +0 -2
  185. package/dist/admin/arms/ArmsField.js +0 -310
  186. package/dist/admin/arms/ParamsEditor.d.ts +0 -16
  187. package/dist/admin/arms/ParamsEditor.js +0 -79
  188. package/dist/admin/copy/tabs.d.ts +0 -7
  189. package/dist/admin/copy/tabs.js +0 -7
  190. package/dist/admin/permissions.model.d.ts +0 -10
  191. package/dist/admin/permissions.model.js +0 -4
  192. package/dist/admin/phaseHistory.model.d.ts +0 -19
  193. package/dist/admin/phaseHistory.model.js +0 -19
  194. package/dist/admin/results/ResultsView.d.ts +0 -3
  195. package/dist/admin/results/ResultsView.js +0 -312
  196. package/dist/admin/review/ReviewPanel.d.ts +0 -2
  197. package/dist/admin/review/ReviewPanel.js +0 -268
  198. package/dist/admin/summaryBatcher.d.ts +0 -8
  199. package/dist/admin/summaryBatcher.js +0 -33
  200. package/dist/admin/targeting/TargetingAttributeField.d.ts +0 -2
  201. package/dist/admin/targeting/TargetingAttributeField.js +0 -31
  202. package/dist/admin/targeting/TargetingOpField.d.ts +0 -2
  203. package/dist/admin/targeting/TargetingOpField.js +0 -45
  204. package/dist/admin/targeting/TargetingValueField.d.ts +0 -2
  205. package/dist/admin/targeting/TargetingValueField.js +0 -142
  206. package/dist/index.d.ts +0 -1
  207. package/dist/index.js +0 -4
@@ -0,0 +1,198 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ import { TEST_PAGE as C } from '../../copy/testPage.js';
5
+ import { metricSourceOptions } from '../../metricSource.model.js';
6
+ import { reviewView } from '../../review/review.model.js';
7
+ import { CheckboxList, FieldErrors, reasonFor, save, SelectEdit, TextEdit } from './fields.js';
8
+ import { Section } from './Section.js';
9
+ export function GoalSection({ test, surface }) {
10
+ const [metrics, setMetrics] = useState([]);
11
+ const [query, setQuery] = useState('');
12
+ const [failed, setFailed] = useState(false);
13
+ const [loaded, setLoaded] = useState(false);
14
+ const [attempt, setAttempt] = useState(0);
15
+ useEffect(()=>{
16
+ const controller = new AbortController();
17
+ void (async ()=>{
18
+ try {
19
+ const all = [];
20
+ let page = 1;
21
+ for(;;){
22
+ const response = await fetch(`${surface.apiRoute.replace(/\/$/, '')}/xp-metrics?depth=0&limit=100&page=${page}`, {
23
+ credentials: 'include',
24
+ signal: controller.signal
25
+ });
26
+ if (!response.ok) throw new Error();
27
+ const body = await response.json();
28
+ if (!Array.isArray(body.docs)) throw new Error();
29
+ all.push(...body.docs);
30
+ if (!body.hasNextPage) break;
31
+ page++;
32
+ }
33
+ if (!controller.signal.aborted) {
34
+ setMetrics(all);
35
+ setFailed(false);
36
+ setLoaded(true);
37
+ }
38
+ } catch {
39
+ if (!controller.signal.aborted) setFailed(true);
40
+ }
41
+ })();
42
+ return ()=>controller.abort();
43
+ }, [
44
+ surface.apiRoute,
45
+ attempt
46
+ ]);
47
+ const doc = test.doc;
48
+ const reason = reasonFor(test, 'metrics');
49
+ const sources = metricSourceOptions(surface.metricSources);
50
+ const unknownGoal = loaded ? C.missingGoal : C.goalUnavailable;
51
+ const options = metrics.map((metric)=>({
52
+ value: String(metric.id),
53
+ label: `${metric.name} · ${sources.find((source)=>source.value === metric.source)?.badge ?? metric.source}`
54
+ }));
55
+ const chosen = [
56
+ doc.metrics?.primary,
57
+ ...doc.metrics?.guardrails ?? [],
58
+ ...doc.metrics?.secondary ?? []
59
+ ].filter((id)=>id != null).map(String);
60
+ const visible = options.filter((option)=>chosen.includes(option.value) || option.label.toLowerCase().includes(query.toLowerCase()));
61
+ for (const id of chosen)if (!visible.some((option)=>option.value === id)) visible.push({
62
+ value: id,
63
+ label: unknownGoal
64
+ });
65
+ const roleOptions = (role)=>{
66
+ const blocked = new Set([
67
+ ...role !== 'primary' && doc.metrics?.primary != null ? [
68
+ doc.metrics.primary
69
+ ] : [],
70
+ ...role !== 'guardrails' ? doc.metrics?.guardrails ?? [] : [],
71
+ ...role !== 'secondary' ? doc.metrics?.secondary ?? [] : []
72
+ ].map(String));
73
+ return visible.filter((option)=>!blocked.has(option.value));
74
+ };
75
+ const originalId = (value)=>metrics.find((metric)=>String(metric.id) === value)?.id ?? [
76
+ test.doc.metrics?.primary,
77
+ ...test.doc.metrics?.guardrails ?? [],
78
+ ...test.doc.metrics?.secondary ?? []
79
+ ].find((id)=>id != null && String(id) === value);
80
+ const changePrimary = (value)=>{
81
+ const id = value ? originalId(value) : null;
82
+ if (id === undefined || id === (test.doc.metrics?.primary ?? null)) return;
83
+ save(test, 'metrics', ()=>({
84
+ metrics: {
85
+ ...test.doc.metrics,
86
+ primary: id
87
+ }
88
+ }));
89
+ };
90
+ const toggle = (role, value, checked)=>save(test, 'metrics', ()=>{
91
+ const current = test.doc.metrics;
92
+ const ids = current?.[role] ?? [];
93
+ const id = originalId(value);
94
+ return {
95
+ metrics: {
96
+ ...current,
97
+ [role]: checked && id != null ? ids.some((saved)=>String(saved) === value) ? ids : [
98
+ ...ids,
99
+ id
100
+ ] : ids.filter((saved)=>String(saved) !== value)
101
+ }
102
+ };
103
+ });
104
+ const display = (ids)=>ids.length ? ids.map((id)=>options.find((option)=>option.value === String(id))?.label ?? unknownGoal).join(', ') : C.noGoal;
105
+ const primary = metrics.find((metric)=>String(metric.id) === String(doc.metrics?.primary));
106
+ const kind = surface.metricSources.find((source)=>source.id === primary?.source)?.kind;
107
+ const baseline = test.preflight?.baseline && String(test.preflight.baseline.metricId) === String(doc.metrics?.primary) ? reviewView(test.preflight, {
108
+ primaryMetricKind: kind
109
+ }).baseline : null;
110
+ return /*#__PURE__*/ _jsxs(Section, {
111
+ test: test,
112
+ sectionId: "win",
113
+ children: [
114
+ !reason ? /*#__PURE__*/ _jsx(TextEdit, {
115
+ label: C.searchGoals,
116
+ value: query,
117
+ onDraft: setQuery,
118
+ onCommit: setQuery
119
+ }) : null,
120
+ failed ? /*#__PURE__*/ _jsxs("p", {
121
+ role: "alert",
122
+ children: [
123
+ C.goalsError,
124
+ " ",
125
+ /*#__PURE__*/ _jsx("button", {
126
+ type: "button",
127
+ onClick: ()=>setAttempt((n)=>n + 1),
128
+ children: C.retry
129
+ })
130
+ ]
131
+ }) : null,
132
+ /*#__PURE__*/ _jsx(SelectEdit, {
133
+ label: C.goal,
134
+ value: String(doc.metrics?.primary ?? ''),
135
+ options: [
136
+ {
137
+ value: '',
138
+ label: C.chooseGoal
139
+ },
140
+ ...roleOptions('primary')
141
+ ],
142
+ reason: reason,
143
+ readOnlyValue: display(doc.metrics?.primary == null ? [] : [
144
+ doc.metrics.primary
145
+ ]),
146
+ onCommit: changePrimary
147
+ }),
148
+ /*#__PURE__*/ _jsx("p", {
149
+ children: C.goalHelp
150
+ }),
151
+ /*#__PURE__*/ _jsx("a", {
152
+ href: `${surface.adminRoute}/collections/xp-metrics`,
153
+ children: C.manageGoals
154
+ }),
155
+ /*#__PURE__*/ _jsx(FieldErrors, {
156
+ test: test,
157
+ path: "metrics.primary"
158
+ }),
159
+ /*#__PURE__*/ _jsx(CheckboxList, {
160
+ label: C.guardrails,
161
+ value: (doc.metrics?.guardrails ?? []).map(String),
162
+ options: roleOptions('guardrails'),
163
+ reason: reason,
164
+ readOnlyValue: display(doc.metrics?.guardrails ?? []),
165
+ onToggle: (value, checked)=>toggle('guardrails', value, checked)
166
+ }),
167
+ /*#__PURE__*/ _jsx("p", {
168
+ children: C.guardrailHelp
169
+ }),
170
+ /*#__PURE__*/ _jsx(FieldErrors, {
171
+ test: test,
172
+ path: "metrics.guardrails"
173
+ }),
174
+ /*#__PURE__*/ _jsx(CheckboxList, {
175
+ label: C.secondary,
176
+ value: (doc.metrics?.secondary ?? []).map(String),
177
+ options: roleOptions('secondary'),
178
+ reason: reason,
179
+ readOnlyValue: display(doc.metrics?.secondary ?? []),
180
+ onToggle: (value, checked)=>toggle('secondary', value, checked)
181
+ }),
182
+ /*#__PURE__*/ _jsx(FieldErrors, {
183
+ test: test,
184
+ path: "metrics.secondary"
185
+ }),
186
+ baseline ? /*#__PURE__*/ _jsxs("p", {
187
+ children: [
188
+ primary?.name ? `${primary.name}: ` : '',
189
+ baseline
190
+ ]
191
+ }) : null,
192
+ test.errors.metrics ? /*#__PURE__*/ _jsx("p", {
193
+ role: "alert",
194
+ children: test.errors.metrics
195
+ }) : null
196
+ ]
197
+ });
198
+ }
@@ -0,0 +1,2 @@
1
+ import type { SectionProps } from '../contracts.js';
2
+ export declare function LengthSection({ test }: SectionProps): import("react").JSX.Element;
@@ -0,0 +1,87 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { TEST_PAGE as C, suggestedWeeks, weeksHint } from '../../copy/testPage.js';
4
+ import { durationSuggestion } from './testPage.model.js';
5
+ import { FieldErrors, positiveInteger, reasonFor, save, TextEdit } from './fields.js';
6
+ import { Section } from './Section.js';
7
+ export function LengthSection({ test }) {
8
+ const doc = test.doc;
9
+ const reason = reasonFor(test, 'analysis');
10
+ const suggestion = durationSuggestion(test.preflight, doc.analysis?.plannedDurationDays);
11
+ const required = [
12
+ 'manual',
13
+ 'viewport'
14
+ ].includes(doc.exposurePolicy);
15
+ const change = (field, raw)=>save(test, 'analysis', ()=>({
16
+ analysis: {
17
+ ...test.doc.analysis,
18
+ [field]: raw.trim() ? Number(raw) : null
19
+ }
20
+ }));
21
+ return /*#__PURE__*/ _jsxs(Section, {
22
+ test: test,
23
+ sectionId: "length",
24
+ children: [
25
+ /*#__PURE__*/ _jsx(TextEdit, {
26
+ label: required ? `${C.reach} ${C.required}` : C.reach,
27
+ value: doc.analysis?.reachPerDay ?? '',
28
+ type: "number",
29
+ min: 1,
30
+ reason: reason,
31
+ required: required,
32
+ onCommit: (value)=>change('reachPerDay', value)
33
+ }),
34
+ /*#__PURE__*/ _jsx("p", {
35
+ children: C.reachHelp
36
+ }),
37
+ /*#__PURE__*/ _jsx(FieldErrors, {
38
+ test: test,
39
+ path: "analysis.reachPerDay"
40
+ }),
41
+ suggestion ? /*#__PURE__*/ _jsxs("div", {
42
+ className: "xp-duration-suggestion",
43
+ children: [
44
+ /*#__PURE__*/ _jsx("p", {
45
+ children: suggestedWeeks(suggestion.weeks)
46
+ }),
47
+ !reason ? /*#__PURE__*/ _jsx("button", {
48
+ type: "button",
49
+ onClick: ()=>save(test, 'analysis', ()=>({
50
+ analysis: {
51
+ ...test.doc.analysis,
52
+ plannedDurationDays: suggestion.plannedDurationDays,
53
+ sequentialTuningParameter: suggestion.sequentialTuningParameter
54
+ }
55
+ })),
56
+ children: C.apply
57
+ }) : null,
58
+ !suggestion.feasible ? /*#__PURE__*/ _jsx("p", {
59
+ children: C.infeasible
60
+ }) : null
61
+ ]
62
+ }) : !test.preflight?.estimate ? /*#__PURE__*/ _jsx("p", {
63
+ children: doc.analysis?.reachPerDay == null ? C.noSuggestion : C.chooseGoalSuggestion
64
+ }) : null,
65
+ /*#__PURE__*/ _jsx(TextEdit, {
66
+ label: C.duration,
67
+ value: doc.analysis?.plannedDurationDays ?? 28,
68
+ type: "number",
69
+ min: 1,
70
+ reason: reason,
71
+ validate: positiveInteger,
72
+ onCommit: (value)=>change('plannedDurationDays', value)
73
+ }),
74
+ /*#__PURE__*/ _jsx("p", {
75
+ children: weeksHint(doc.analysis?.plannedDurationDays ?? 28)
76
+ }),
77
+ /*#__PURE__*/ _jsx(FieldErrors, {
78
+ test: test,
79
+ path: "analysis.plannedDurationDays"
80
+ }),
81
+ test.errors.analysis ? /*#__PURE__*/ _jsx("p", {
82
+ role: "alert",
83
+ children: test.errors.analysis
84
+ }) : null
85
+ ]
86
+ });
87
+ }
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { SectionId, TestDocApi } from '../contracts.js';
3
+ export declare function Section({ test, sectionId, children }: {
4
+ test: TestDocApi;
5
+ sectionId: SectionId;
6
+ children: ReactNode;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { readinessParts } from '../readiness/parts.js';
4
+ import { TEST_PAGE as C } from '../../copy/testPage.js';
5
+ export function Section({ test, sectionId, children }) {
6
+ const done = readinessParts.sectionDone(test)[sectionId];
7
+ return /*#__PURE__*/ _jsxs("section", {
8
+ className: "xp-test-section",
9
+ id: `section-${sectionId}`,
10
+ "aria-labelledby": `title-${sectionId}`,
11
+ children: [
12
+ /*#__PURE__*/ _jsxs("h2", {
13
+ id: `title-${sectionId}`,
14
+ children: [
15
+ /*#__PURE__*/ _jsx("span", {
16
+ className: done ? 'xp-section-check xp-section-check--done' : 'xp-section-check',
17
+ "aria-label": done ? C.complete : C.incomplete,
18
+ children: done ? C.completeMark : C.incompleteMark
19
+ }),
20
+ ' ',
21
+ C.sections[sectionId]
22
+ ]
23
+ }),
24
+ children
25
+ ]
26
+ });
27
+ }
@@ -0,0 +1,2 @@
1
+ import type { SectionProps } from '../contracts.js';
2
+ export declare function TargetingSection({ test, surface }: SectionProps): import("react").JSX.Element;
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { coerceValue, emptyListHint, humaniseAttribute, opLabel, opsFor, valueInput } from '../../targeting/targeting.model.js';
4
+ import { TEST_PAGE as C, NUMBER_COMPARISONS } from '../../copy/testPage.js';
5
+ import { CheckboxList, FieldErrors, reasonFor, save, SelectEdit, TextEdit } from './fields.js';
6
+ import { Section } from './Section.js';
7
+ export function TargetingSection({ test, surface }) {
8
+ const doc = test.doc;
9
+ const rules = doc.targeting ?? [];
10
+ const reason = reasonFor(test, 'targeting');
11
+ const update = (transform)=>save(test, 'targeting', ()=>({
12
+ targeting: transform(test.doc.targeting ?? [])
13
+ }));
14
+ const keys = Object.keys(surface.attributeKeys);
15
+ return /*#__PURE__*/ _jsx(Section, {
16
+ test: test,
17
+ sectionId: "who",
18
+ children: /*#__PURE__*/ _jsxs("details", {
19
+ children: [
20
+ /*#__PURE__*/ _jsx("summary", {
21
+ children: rules.length ? C.rulesSummary : C.everyone
22
+ }),
23
+ rules.map((rule, index)=>{
24
+ const attribute = surface.attributeKeys[rule.attribute];
25
+ const input = valueInput(attribute, rule.op);
26
+ const setRule = (partial)=>update((current)=>current.map((row, i)=>i === index ? {
27
+ ...row,
28
+ ...partial
29
+ } : row));
30
+ const hint = emptyListHint(rule.op, rule.value);
31
+ const changeValue = (raw)=>{
32
+ const current = test.doc.targeting?.[index];
33
+ const schema = current && surface.attributeKeys[current.attribute];
34
+ if (!current || !schema) return;
35
+ const value = coerceValue(schema.type, current.op, raw);
36
+ if (value !== null) setRule({
37
+ value
38
+ });
39
+ };
40
+ return /*#__PURE__*/ _jsxs("div", {
41
+ className: "xp-targeting-row",
42
+ children: [
43
+ /*#__PURE__*/ _jsx(SelectEdit, {
44
+ label: C.attribute,
45
+ reason: reason,
46
+ value: rule.attribute,
47
+ options: [
48
+ ...keys,
49
+ ...!attribute ? [
50
+ rule.attribute
51
+ ] : []
52
+ ].map((key)=>({
53
+ value: key,
54
+ label: humaniseAttribute(key)
55
+ })),
56
+ onCommit: (value)=>{
57
+ const key = String(value);
58
+ const type = surface.attributeKeys[key].type;
59
+ setRule({
60
+ attribute: key,
61
+ op: 'eq',
62
+ value: type === 'boolean' ? false : type === 'number' ? 0 : ''
63
+ });
64
+ }
65
+ }),
66
+ /*#__PURE__*/ _jsx(SelectEdit, {
67
+ label: C.comparison,
68
+ reason: reason,
69
+ value: rule.op,
70
+ options: opsFor(attribute?.type).map((op)=>({
71
+ value: op,
72
+ label: attribute?.type === 'number' ? NUMBER_COMPARISONS[op] : opLabel(attribute.type, op)
73
+ })),
74
+ onCommit: (value)=>{
75
+ const op = value;
76
+ setRule({
77
+ op,
78
+ value: coerceValue(attribute.type, op, test.doc.targeting?.[index]?.value) ?? ''
79
+ });
80
+ }
81
+ }),
82
+ input.kind === 'multiselect' ? /*#__PURE__*/ _jsx(CheckboxList, {
83
+ label: C.value,
84
+ reason: reason,
85
+ value: Array.isArray(rule.value) ? rule.value.map(String) : [],
86
+ options: input.options ?? [],
87
+ onToggle: (value, checked)=>{
88
+ const current = test.doc.targeting?.[index]?.value;
89
+ const values = Array.isArray(current) ? current.map(String) : [];
90
+ changeValue(checked ? [
91
+ ...new Set([
92
+ ...values,
93
+ value
94
+ ])
95
+ ] : values.filter((saved)=>saved !== value));
96
+ }
97
+ }) : input.kind === 'select' ? /*#__PURE__*/ _jsx(SelectEdit, {
98
+ label: C.value,
99
+ reason: reason,
100
+ value: String(rule.value),
101
+ options: input.options ?? [],
102
+ onCommit: changeValue
103
+ }) : input.kind === 'boolean' ? /*#__PURE__*/ _jsx(SelectEdit, {
104
+ label: C.value,
105
+ reason: reason,
106
+ value: String(rule.value),
107
+ options: [
108
+ {
109
+ value: 'true',
110
+ label: C.yes
111
+ },
112
+ {
113
+ value: 'false',
114
+ label: C.no
115
+ }
116
+ ],
117
+ onCommit: changeValue
118
+ }) : /*#__PURE__*/ _jsx(TextEdit, {
119
+ label: C.value,
120
+ reason: reason,
121
+ value: Array.isArray(rule.value) ? rule.value.join(', ') : String(rule.value),
122
+ type: input.kind === 'number' ? 'number' : 'text',
123
+ onCommit: changeValue
124
+ }),
125
+ hint ? /*#__PURE__*/ _jsx("p", {
126
+ children: hint
127
+ }) : null,
128
+ /*#__PURE__*/ _jsx(FieldErrors, {
129
+ test: test,
130
+ path: `targeting.${index}`
131
+ }),
132
+ !reason ? /*#__PURE__*/ _jsx("button", {
133
+ type: "button",
134
+ onClick: ()=>update((current)=>current.filter((_, i)=>i !== index)),
135
+ children: C.removeRule
136
+ }) : null
137
+ ]
138
+ }, index);
139
+ }),
140
+ !reason ? /*#__PURE__*/ _jsx("button", {
141
+ type: "button",
142
+ disabled: !keys.length,
143
+ onClick: ()=>{
144
+ const attribute = keys[0];
145
+ const type = surface.attributeKeys[attribute].type;
146
+ update((current)=>[
147
+ ...current,
148
+ {
149
+ attribute,
150
+ op: 'eq',
151
+ value: type === 'boolean' ? false : type === 'number' ? 0 : ''
152
+ }
153
+ ]);
154
+ },
155
+ children: C.addRule
156
+ }) : null,
157
+ /*#__PURE__*/ _jsx(FieldErrors, {
158
+ test: test,
159
+ path: "targeting"
160
+ })
161
+ ]
162
+ })
163
+ });
164
+ }
@@ -0,0 +1,4 @@
1
+ import type { SectionProps } from '../contracts.js';
2
+ export declare function TestHeader({ test, surface, view }: SectionProps & {
3
+ view?: 'setup' | 'results';
4
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { formatAdminURL } from 'payload/shared';
4
+ import { SURFACE_COPY } from '../../copy/surface.js';
5
+ import { readinessParts } from '../readiness/parts.js';
6
+ import { statusPill } from '../../status.model.js';
7
+ import { TEST_PAGE as C } from '../../copy/testPage.js';
8
+ import { FieldErrors, reasonFor, save, TextEdit } from './fields.js';
9
+ export function TestHeader({ test, surface, view = 'setup' }) {
10
+ const doc = test.doc;
11
+ if (!doc) return null;
12
+ const pill = statusPill({
13
+ status: doc.status,
14
+ arms: doc.arms,
15
+ decision: doc.decision
16
+ });
17
+ return /*#__PURE__*/ _jsxs("header", {
18
+ className: "xp-test-header",
19
+ children: [
20
+ /*#__PURE__*/ _jsx(TextEdit, {
21
+ label: C.name,
22
+ value: doc.name,
23
+ reason: reasonFor(test, 'name'),
24
+ onCommit: (value)=>save(test, 'name', ()=>({
25
+ name: value
26
+ }))
27
+ }),
28
+ /*#__PURE__*/ _jsx(FieldErrors, {
29
+ test: test,
30
+ path: "name"
31
+ }),
32
+ /*#__PURE__*/ _jsxs("div", {
33
+ className: "xp-header-meta",
34
+ children: [
35
+ /*#__PURE__*/ _jsx("span", {
36
+ className: "xp-kind-chip",
37
+ children: C.kinds[doc.kind]
38
+ }),
39
+ /*#__PURE__*/ _jsx("span", {
40
+ className: `xp-status-pill xp-status-pill--${pill.tone}`,
41
+ title: pill.tooltip,
42
+ children: pill.label
43
+ }),
44
+ /*#__PURE__*/ _jsx(readinessParts.SaveStatus, {
45
+ test: test
46
+ }),
47
+ view === 'results' ? /*#__PURE__*/ _jsx("a", {
48
+ href: formatAdminURL({
49
+ adminRoute: surface.adminRoute,
50
+ path: `/experiments/${encodeURIComponent(String(doc.id))}`
51
+ }),
52
+ children: SURFACE_COPY.backToSetup
53
+ }) : surface.canReadResults && [
54
+ 'running',
55
+ 'stopped',
56
+ 'decided'
57
+ ].includes(doc.status) ? /*#__PURE__*/ _jsx("a", {
58
+ href: `${surface.adminRoute}/experiments/${encodeURIComponent(String(doc.id))}/results`,
59
+ children: C.results
60
+ }) : null
61
+ ]
62
+ }),
63
+ /*#__PURE__*/ _jsx(readinessParts.Banner, {
64
+ test: test,
65
+ surface: surface
66
+ })
67
+ ]
68
+ });
69
+ }
@@ -0,0 +1,7 @@
1
+ import type { DocId } from '../../../types/contracts.js';
2
+ import type { SurfaceProps } from '../contracts.js';
3
+ import './testPage.css';
4
+ export declare function TestPage({ id, surface }: {
5
+ id: DocId;
6
+ surface: SurfaceProps;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTestDocument } from '../useTestDocument.js';
4
+ import { TEST_PAGE as C } from '../../copy/testPage.js';
5
+ import { pageBlockParts } from '../pageBlock/parts.js';
6
+ import { settingParts } from '../setting/parts.js';
7
+ import { pageParts } from './pageParts.js';
8
+ import { TestHeader } from './TestHeader.js';
9
+ import { Section } from './Section.js';
10
+ import { Hypothesis } from './fields.js';
11
+ import { VersionsSection } from './VersionsSection.js';
12
+ import { GoalSection } from './GoalSection.js';
13
+ import { TargetingSection } from './TargetingSection.js';
14
+ import { LengthSection } from './LengthSection.js';
15
+ import { AdvancedSection } from './AdvancedSection.js';
16
+ import './testPage.css';
17
+ export function TestPage({ id, surface }) {
18
+ const test = useTestDocument(id, {
19
+ apiRoute: surface.apiRoute
20
+ });
21
+ if (test.loadState !== 'ready' || !test.doc) return /*#__PURE__*/ _jsxs("div", {
22
+ className: "xp-test-page",
23
+ role: test.loadState === 'error' ? 'alert' : 'status',
24
+ children: [
25
+ /*#__PURE__*/ _jsx("p", {
26
+ children: test.loadState === 'forbidden' ? C.forbidden : test.loadState === 'missing' ? C.missing : test.loadState === 'error' ? C.error : C.loading
27
+ }),
28
+ test.loadState === 'error' ? /*#__PURE__*/ _jsx("button", {
29
+ type: "button",
30
+ onClick: ()=>void test.reload(),
31
+ children: C.retry
32
+ }) : null
33
+ ]
34
+ });
35
+ const parts = test.doc.kind === 'block' ? pageBlockParts : test.doc.kind === 'page' ? pageParts : settingParts;
36
+ const props = {
37
+ test,
38
+ surface
39
+ };
40
+ return /*#__PURE__*/ _jsxs("main", {
41
+ className: "xp-test-page",
42
+ children: [
43
+ /*#__PURE__*/ _jsx(TestHeader, {
44
+ ...props
45
+ }),
46
+ test.errors._section ? /*#__PURE__*/ _jsx("p", {
47
+ role: "alert",
48
+ children: test.errors._section
49
+ }) : null,
50
+ test.preflightError ? /*#__PURE__*/ _jsx("p", {
51
+ role: "alert",
52
+ children: test.preflightError
53
+ }) : null,
54
+ /*#__PURE__*/ _jsxs(Section, {
55
+ test: test,
56
+ sectionId: "what",
57
+ children: [
58
+ /*#__PURE__*/ _jsx(parts.Binding, {
59
+ ...props
60
+ }),
61
+ /*#__PURE__*/ _jsx(Hypothesis, {
62
+ ...props
63
+ })
64
+ ]
65
+ }),
66
+ /*#__PURE__*/ _jsx(VersionsSection, {
67
+ ...props,
68
+ parts: parts
69
+ }),
70
+ /*#__PURE__*/ _jsx(GoalSection, {
71
+ ...props
72
+ }),
73
+ /*#__PURE__*/ _jsx(TargetingSection, {
74
+ ...props
75
+ }),
76
+ /*#__PURE__*/ _jsx(LengthSection, {
77
+ ...props
78
+ }),
79
+ /*#__PURE__*/ _jsx(AdvancedSection, {
80
+ ...props
81
+ })
82
+ ]
83
+ });
84
+ }
@@ -0,0 +1,4 @@
1
+ import type { KindParts, SectionProps } from '../contracts.js';
2
+ export declare function VersionsSection({ test, surface, parts }: SectionProps & {
3
+ parts: KindParts;
4
+ }): import("react").JSX.Element;