@delmaredigital/payload-experiments 0.3.0-rc.4 → 0.4.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 (299) hide show
  1. package/README.md +122 -27
  2. package/dist/admin/MetricSourceSelect.js +3 -3
  3. package/dist/admin/PlacementStatus.d.ts +3 -0
  4. package/dist/admin/PlacementStatus.js +54 -0
  5. package/dist/admin/PreviewLinks.d.ts +2 -1
  6. package/dist/admin/PreviewLinks.js +2 -2
  7. package/dist/admin/TestsDashboardCards.d.ts +1 -0
  8. package/dist/admin/TestsDashboardCards.js +4 -8
  9. package/dist/admin/TestsNavLink.d.ts +1 -0
  10. package/dist/admin/TestsNavLink.js +4 -10
  11. package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
  12. package/dist/admin/actions/ConfirmDialog.js +2 -2
  13. package/dist/admin/actions/DecideDialog.d.ts +2 -1
  14. package/dist/admin/actions/DecideDialog.js +2 -2
  15. package/dist/admin/api.d.ts +18 -2
  16. package/dist/admin/api.js +47 -2
  17. package/dist/admin/blocks/BlockPanel.d.ts +4 -0
  18. package/dist/admin/blocks/BlockPanel.js +199 -0
  19. package/dist/admin/blocks/TestBlockField.d.ts +5 -0
  20. package/dist/admin/blocks/TestBlockField.js +163 -0
  21. package/dist/admin/blocks/formApi.d.ts +46 -0
  22. package/dist/admin/blocks/formApi.js +159 -0
  23. package/dist/admin/blocks/inject.d.ts +7 -0
  24. package/dist/admin/blocks/inject.js +41 -0
  25. package/dist/admin/blocks/model.d.ts +17 -0
  26. package/dist/admin/blocks/model.js +45 -0
  27. package/dist/admin/blocks/statusCache.d.ts +9 -0
  28. package/dist/admin/blocks/statusCache.js +92 -0
  29. package/dist/admin/blocks/useBlockForm.d.ts +17 -0
  30. package/dist/admin/blocks/useBlockForm.js +46 -0
  31. package/dist/admin/copy/actions.d.ts +2 -0
  32. package/dist/admin/copy/actions.js +9 -0
  33. package/dist/admin/copy/blocks.d.ts +43 -0
  34. package/dist/admin/copy/blocks.js +64 -0
  35. package/dist/admin/copy/common.d.ts +1 -0
  36. package/dist/admin/copy/common.js +1 -1
  37. package/dist/admin/copy/goals.d.ts +2 -0
  38. package/dist/admin/copy/goals.js +4 -1
  39. package/dist/admin/copy/newTest.d.ts +10 -0
  40. package/dist/admin/copy/newTest.js +10 -0
  41. package/dist/admin/copy/pages.d.ts +24 -0
  42. package/dist/admin/copy/pages.js +24 -0
  43. package/dist/admin/copy/readiness.d.ts +23 -3
  44. package/dist/admin/copy/readiness.js +24 -3
  45. package/dist/admin/copy/results.d.ts +1 -0
  46. package/dist/admin/copy/results.js +1 -0
  47. package/dist/admin/copy/testPage.d.ts +4 -0
  48. package/dist/admin/copy/testPage.js +4 -0
  49. package/dist/admin/goals/XpGoalField.d.ts +4 -0
  50. package/dist/admin/goals/XpGoalField.js +168 -0
  51. package/dist/admin/goals/xpGoalField.css +48 -0
  52. package/dist/admin/metricSource.model.d.ts +2 -0
  53. package/dist/admin/metricSource.model.js +4 -0
  54. package/dist/admin/pageCollections.d.ts +10 -0
  55. package/dist/admin/pageCollections.js +20 -0
  56. package/dist/admin/paths.d.ts +9 -0
  57. package/dist/admin/paths.js +9 -0
  58. package/dist/admin/placementStatus.model.d.ts +2 -0
  59. package/dist/admin/placementStatus.model.js +19 -0
  60. package/dist/admin/placements/placementLabels.d.ts +11 -0
  61. package/dist/admin/placements/placementLabels.js +21 -0
  62. package/dist/admin/placements/placementLabels.model.d.ts +13 -0
  63. package/dist/admin/placements/placementLabels.model.js +12 -0
  64. package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
  65. package/dist/admin/placements/usePlacementLabels.js +102 -0
  66. package/dist/admin/results/DataQualityPanel.js +4 -1
  67. package/dist/admin/results/results.model.d.ts +3 -0
  68. package/dist/admin/results/results.model.js +8 -4
  69. package/dist/admin/surface/TestsHome.js +39 -10
  70. package/dist/admin/surface/TestsSurface.js +7 -6
  71. package/dist/admin/surface/contracts.d.ts +6 -5
  72. package/dist/admin/surface/goals/GoalForm.js +71 -44
  73. package/dist/admin/surface/goals/GoalsList.js +5 -4
  74. package/dist/admin/surface/goals/data.js +4 -1
  75. package/dist/admin/surface/goals/goals.model.d.ts +1 -2
  76. package/dist/admin/surface/goals/goals.model.js +1 -3
  77. package/dist/admin/surface/home.model.d.ts +3 -3
  78. package/dist/admin/surface/home.model.js +8 -4
  79. package/dist/admin/surface/newTest/NewTest.js +56 -17
  80. package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
  81. package/dist/admin/surface/newTest/PageWizard.js +214 -0
  82. package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
  83. package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
  84. package/dist/admin/surface/pagePicker/PageCombobox.d.ts +10 -0
  85. package/dist/admin/surface/pagePicker/PageCombobox.js +395 -0
  86. package/dist/admin/surface/pagePicker/pagePicker.css +10 -0
  87. package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +27 -0
  88. package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
  89. package/dist/admin/surface/readiness/ActionDialogs.js +99 -64
  90. package/dist/admin/surface/readiness/Banner.js +30 -6
  91. package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
  92. package/dist/admin/surface/readiness/readiness.model.js +92 -9
  93. package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
  94. package/dist/admin/surface/results/ReleasePages.js +3 -7
  95. package/dist/admin/surface/results/ResultsBody.js +21 -2
  96. package/dist/admin/surface/results/ResultsHeader.js +5 -13
  97. package/dist/admin/surface/results/useResults.d.ts +20 -1
  98. package/dist/admin/surface/test/AdvancedSection.js +111 -104
  99. package/dist/admin/surface/test/GoalSection.js +94 -14
  100. package/dist/admin/surface/test/LengthSection.js +1 -1
  101. package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
  102. package/dist/admin/surface/test/PageHoldout.js +67 -0
  103. package/dist/admin/surface/test/TestPage.js +5 -9
  104. package/dist/admin/surface/test/VersionsSection.js +12 -5
  105. package/dist/admin/surface/test/fields.d.ts +13 -4
  106. package/dist/admin/surface/test/fields.js +46 -14
  107. package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
  108. package/dist/admin/surface/test/jumpToSection.js +4 -3
  109. package/dist/admin/surface/test/pageParts.d.ts +4 -1
  110. package/dist/admin/surface/test/pageParts.js +49 -1
  111. package/dist/admin/surface/test/testPage.css +3 -1
  112. package/dist/admin/surface/testDocument.model.d.ts +1 -0
  113. package/dist/admin/surface/testDocument.model.js +2 -1
  114. package/dist/admin/surface/useTestDocument.d.ts +15 -2
  115. package/dist/admin/surface/useTestDocument.js +53 -15
  116. package/dist/admin/surfaceUrls.d.ts +7 -0
  117. package/dist/admin/surfaceUrls.js +19 -0
  118. package/dist/admin/theme.css +2 -0
  119. package/dist/admin/useApi.d.ts +21 -2
  120. package/dist/admin/useApi.js +3 -5
  121. package/dist/admin/views/CollectionListRedirect.d.ts +1 -0
  122. package/dist/admin/views/CollectionListRedirect.js +1 -1
  123. package/dist/admin/views/ExperimentsRootView.js +28 -133
  124. package/dist/admin/views/GoalEditRedirect.d.ts +4 -0
  125. package/dist/admin/views/GoalEditRedirect.js +14 -0
  126. package/dist/admin/views/GoalsCollectionListView.d.ts +2 -0
  127. package/dist/admin/views/GoalsCollectionListView.js +4 -0
  128. package/dist/admin/views/TestsCollectionListView.d.ts +2 -0
  129. package/dist/admin/views/TestsCollectionListView.js +4 -0
  130. package/dist/admin/views/TestsNavLinkServer.d.ts +1 -0
  131. package/dist/admin/views/TestsNavLinkServer.js +1 -1
  132. package/dist/admin/views/buildSurfaceProps.d.ts +8 -0
  133. package/dist/admin/views/buildSurfaceProps.js +143 -0
  134. package/dist/admin/views/renderCollectionList.d.ts +2 -0
  135. package/dist/admin/views/renderCollectionList.js +34 -0
  136. package/dist/admin/views/renderTestsSurface.d.ts +3 -0
  137. package/dist/admin/views/renderTestsSurface.js +34 -0
  138. package/dist/blocks/bindings.d.ts +3 -0
  139. package/dist/blocks/bindings.js +34 -0
  140. package/dist/blocks/createExperimentBlock.d.ts +6 -0
  141. package/dist/blocks/createExperimentBlock.js +77 -0
  142. package/dist/blocks/formState.d.ts +31 -0
  143. package/dist/blocks/formState.js +146 -0
  144. package/dist/blocks/model.d.ts +41 -0
  145. package/dist/blocks/model.js +34 -0
  146. package/dist/client/ExperimentGroup.d.ts +6 -0
  147. package/dist/client/ExperimentGroup.js +9 -0
  148. package/dist/client/ExperimentsProvider.js +20 -4
  149. package/dist/client/clickGoals.d.ts +4 -0
  150. package/dist/client/clickGoals.js +43 -0
  151. package/dist/collections/experiments/config.js +21 -7
  152. package/dist/collections/experiments/deleteGuard.d.ts +2 -1
  153. package/dist/collections/experiments/deleteGuard.js +12 -1
  154. package/dist/collections/experiments/editability.js +4 -2
  155. package/dist/collections/experiments/pickSlug.d.ts +2 -0
  156. package/dist/collections/experiments/pickSlug.js +8 -0
  157. package/dist/collections/labels.d.ts +3 -0
  158. package/dist/collections/labels.js +20 -0
  159. package/dist/collections/metrics.js +28 -6
  160. package/dist/collections/phases.js +1 -0
  161. package/dist/collections/serialize.js +7 -0
  162. package/dist/db/clickGoals.d.ts +9 -0
  163. package/dist/db/clickGoals.js +22 -0
  164. package/dist/db/delete.d.ts +1 -0
  165. package/dist/db/delete.js +1 -0
  166. package/dist/db/locks.d.ts +8 -0
  167. package/dist/db/locks.js +27 -4
  168. package/dist/db/names.d.ts +4 -0
  169. package/dist/db/names.js +4 -0
  170. package/dist/db/pageBindings.d.ts +4 -0
  171. package/dist/db/pageBindings.js +17 -0
  172. package/dist/db/pageBusy.d.ts +2 -0
  173. package/dist/db/pageBusy.js +10 -0
  174. package/dist/db/pageContent.d.ts +10 -0
  175. package/dist/db/pageContent.js +79 -0
  176. package/dist/db/placements.d.ts +33 -0
  177. package/dist/db/placements.js +54 -0
  178. package/dist/db/slugs.d.ts +8 -0
  179. package/dist/db/slugs.js +40 -0
  180. package/dist/db/snapshots.d.ts +6 -0
  181. package/dist/db/snapshots.js +8 -5
  182. package/dist/endpoints/clickKeys.d.ts +5 -0
  183. package/dist/endpoints/clickKeys.js +17 -0
  184. package/dist/endpoints/events.js +4 -1
  185. package/dist/endpoints/index.js +28 -0
  186. package/dist/endpoints/pages.d.ts +5 -0
  187. package/dist/endpoints/pages.js +120 -0
  188. package/dist/endpoints/placementStatus.d.ts +6 -0
  189. package/dist/endpoints/placementStatus.js +238 -0
  190. package/dist/endpoints/readBody.d.ts +2 -0
  191. package/dist/endpoints/readBody.js +24 -0
  192. package/dist/endpoints/results.js +5 -1
  193. package/dist/endpoints/summary.js +11 -4
  194. package/dist/endpoints/wrapBlock.d.ts +6 -0
  195. package/dist/endpoints/wrapBlock.js +76 -0
  196. package/dist/errors.d.ts +3 -3
  197. package/dist/errors.js +4 -4
  198. package/dist/events/rows.d.ts +1 -1
  199. package/dist/events/rows.js +5 -3
  200. package/dist/exports/adminClient.d.ts +5 -0
  201. package/dist/exports/adminClient.js +5 -0
  202. package/dist/exports/adminRsc.d.ts +3 -0
  203. package/dist/exports/adminRsc.js +3 -0
  204. package/dist/exports/blocks.d.ts +2 -0
  205. package/dist/exports/blocks.js +1 -0
  206. package/dist/exports/client.d.ts +1 -0
  207. package/dist/exports/client.js +1 -0
  208. package/dist/exports/index.d.ts +6 -1
  209. package/dist/exports/index.js +3 -0
  210. package/dist/exports/puck.d.ts +3 -1
  211. package/dist/exports/puck.js +2 -0
  212. package/dist/exports/puckEditor.d.ts +3 -0
  213. package/dist/exports/puckEditor.js +2 -0
  214. package/dist/exports/rsc.d.ts +3 -0
  215. package/dist/exports/rsc.js +2 -0
  216. package/dist/fields/xpGoal.d.ts +3 -0
  217. package/dist/fields/xpGoal.js +16 -0
  218. package/dist/globals/state.js +10 -0
  219. package/dist/goals/cache.d.ts +3 -0
  220. package/dist/goals/cache.js +35 -0
  221. package/dist/goals/fairness.d.ts +40 -0
  222. package/dist/goals/fairness.js +200 -0
  223. package/dist/goals/key.d.ts +4 -0
  224. package/dist/goals/key.js +3 -0
  225. package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
  226. package/dist/goals/picker/clickGoalPicker.model.js +45 -0
  227. package/dist/goals/picker/copy.d.ts +16 -0
  228. package/dist/goals/picker/copy.js +15 -0
  229. package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
  230. package/dist/goals/picker/useClickGoalPicker.js +220 -0
  231. package/dist/goals/validate.d.ts +3 -0
  232. package/dist/goals/validate.js +14 -0
  233. package/dist/hooks/pageGuard.d.ts +5 -1
  234. package/dist/hooks/pageGuard.js +157 -95
  235. package/dist/hooks/puckGoals.d.ts +3 -0
  236. package/dist/hooks/puckGoals.js +38 -0
  237. package/dist/onInit.js +29 -0
  238. package/dist/options.d.ts +2 -0
  239. package/dist/options.js +41 -1
  240. package/dist/plugin.js +50 -43
  241. package/dist/puck/copy.d.ts +1 -0
  242. package/dist/puck/copy.js +1 -0
  243. package/dist/puck/editor/editor.chrome.css +14 -0
  244. package/dist/puck/editor/editor.css +106 -0
  245. package/dist/puck/editor/editor.model.d.ts +4 -2
  246. package/dist/puck/editor/editor.model.js +6 -3
  247. package/dist/puck/editor/integration.d.ts +10 -0
  248. package/dist/puck/editor/integration.js +30 -0
  249. package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
  250. package/dist/puck/editor/xpGoalPuckEditorField.js +142 -0
  251. package/dist/puck/integration.d.ts +6 -0
  252. package/dist/puck/integration.js +21 -0
  253. package/dist/puck/xpGoalField.d.ts +3 -0
  254. package/dist/puck/xpGoalField.js +6 -0
  255. package/dist/results/health.d.ts +4 -0
  256. package/dist/results/health.js +27 -0
  257. package/dist/results/snapshot.js +15 -2
  258. package/dist/sanitizeReadinessDetail.js +43 -0
  259. package/dist/server/pageKindSeoContract.d.ts +27 -0
  260. package/dist/server/pageKindSeoContract.js +80 -0
  261. package/dist/server/recordContentChange.js +58 -8
  262. package/dist/server/resolveExperimentBlocks.d.ts +5 -0
  263. package/dist/server/resolveExperimentBlocks.js +54 -0
  264. package/dist/server/runResults.js +56 -15
  265. package/dist/server/toClientAssignments.js +2 -1
  266. package/dist/transitions/addMetric.d.ts +2 -1
  267. package/dist/transitions/addMetric.js +28 -1
  268. package/dist/transitions/checkStart.d.ts +14 -1
  269. package/dist/transitions/checkStart.js +92 -8
  270. package/dist/transitions/decide.js +25 -0
  271. package/dist/transitions/deleteExperiment.d.ts +3 -1
  272. package/dist/transitions/deleteExperiment.js +27 -1
  273. package/dist/transitions/kernel.js +4 -0
  274. package/dist/transitions/makeContent.d.ts +5 -0
  275. package/dist/transitions/makeContent.js +39 -11
  276. package/dist/transitions/pageCollection.d.ts +10 -0
  277. package/dist/transitions/pageCollection.js +20 -0
  278. package/dist/transitions/pageVersions.d.ts +10 -0
  279. package/dist/transitions/pageVersions.js +44 -0
  280. package/dist/transitions/phaseRecord.d.ts +1 -0
  281. package/dist/transitions/placementInteractions.d.ts +40 -0
  282. package/dist/transitions/placementInteractions.js +94 -0
  283. package/dist/transitions/placements.d.ts +8 -0
  284. package/dist/transitions/placements.js +148 -1
  285. package/dist/transitions/preflightData.d.ts +2 -2
  286. package/dist/transitions/preflightData.js +137 -5
  287. package/dist/transitions/resolveCaps.d.ts +1 -0
  288. package/dist/transitions/resolveCaps.js +6 -2
  289. package/dist/transitions/start.js +46 -1
  290. package/dist/types/admin.d.ts +1 -1
  291. package/dist/types/assignments.d.ts +1 -0
  292. package/dist/types/contracts.d.ts +15 -0
  293. package/dist/types/endpoints.d.ts +92 -5
  294. package/dist/types/index.d.ts +3 -0
  295. package/dist/types/puck.d.ts +7 -1
  296. package/dist/types/snapshot.d.ts +17 -4
  297. package/dist/types/surface.d.ts +13 -0
  298. package/dist/ui/ui.css +4 -0
  299. package/package.json +8 -1
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ADVANCED_COPY as C } from '../../copy/advanced.js';
4
4
  import { exposureOptions } from './testPage.model.js';
5
5
  import { FieldErrors, positiveInteger, ReadOnly, reasonFor, save, SelectEdit, TextEdit } from './fields.js';
6
+ import { PageHoldout } from './PageHoldout.js';
6
7
  import { Section } from './Section.js';
7
8
  const readOnlyFields = [
8
9
  'layer',
@@ -36,125 +37,131 @@ export function AdvancedSection({ test, surface }) {
36
37
  0
37
38
  ]
38
39
  ];
39
- return /*#__PURE__*/ _jsx(Section, {
40
+ return /*#__PURE__*/ _jsxs(Section, {
40
41
  test: test,
41
42
  sectionId: "advanced",
42
- children: /*#__PURE__*/ _jsxs("div", {
43
- className: "xp-field-grid",
44
- children: [
45
- /*#__PURE__*/ _jsxs("div", {
46
- children: [
47
- /*#__PURE__*/ _jsx(SelectEdit, {
48
- label: C.exposurePolicy.label,
49
- value: doc.exposurePolicy,
50
- reason: reasonFor(test, 'exposurePolicy'),
51
- options: exposureOptions(doc.kind).map((value)=>({
52
- value,
53
- label: C.policies[value]
54
- })),
55
- onCommit: (value)=>save(test, 'exposurePolicy', ()=>({
56
- exposurePolicy: value
57
- }))
58
- }),
59
- /*#__PURE__*/ _jsx("p", {
60
- children: C.exposurePolicy.help
61
- }),
62
- /*#__PURE__*/ _jsx(FieldErrors, {
63
- test: test,
64
- path: "exposurePolicy"
65
- })
66
- ]
67
- }),
68
- surface.isAdmin ? /*#__PURE__*/ _jsxs("div", {
69
- children: [
70
- /*#__PURE__*/ _jsx(SelectEdit, {
71
- label: C.method.label,
72
- value: doc.analysis?.method ?? 'sequential',
73
- reason: reason,
74
- options: [
75
- {
76
- value: 'sequential',
77
- label: C.sequential
78
- },
79
- {
80
- value: 'bayesian',
81
- label: C.bayesian
82
- }
83
- ],
84
- onCommit: (value)=>change('method', value)
85
- }),
86
- /*#__PURE__*/ _jsx("p", {
87
- children: C.method.help
88
- }),
89
- /*#__PURE__*/ _jsx(FieldErrors, {
90
- test: test,
91
- path: "analysis.method"
92
- })
93
- ]
94
- }) : null,
95
- /*#__PURE__*/ _jsxs("div", {
96
- children: [
97
- /*#__PURE__*/ _jsx(SelectEdit, {
98
- label: C.cuped.label,
99
- value: doc.analysis?.cuped ?? 'auto',
100
- reason: reason,
101
- options: [
102
- {
103
- value: 'auto',
104
- label: C.auto
105
- },
106
- {
107
- value: 'off',
108
- label: C.off
109
- }
110
- ],
111
- onCommit: (value)=>change('cuped', value)
112
- }),
113
- /*#__PURE__*/ _jsx("p", {
114
- children: C.cuped.help
115
- }),
116
- /*#__PURE__*/ _jsx(FieldErrors, {
117
- test: test,
118
- path: "analysis.cuped"
119
- })
120
- ]
121
- }),
122
- numbers.map(([field, fallback, min])=>/*#__PURE__*/ _jsxs("div", {
43
+ children: [
44
+ doc.kind === 'page' ? /*#__PURE__*/ _jsx(PageHoldout, {
45
+ test: test,
46
+ surface: surface
47
+ }) : null,
48
+ /*#__PURE__*/ _jsxs("div", {
49
+ className: "xp-field-grid",
50
+ children: [
51
+ /*#__PURE__*/ _jsxs("div", {
123
52
  children: [
124
- /*#__PURE__*/ _jsx(TextEdit, {
125
- label: C[field].label,
126
- value: doc.analysis?.[field] ?? fallback,
127
- placeholder: field === 'attributionWindowHours' ? C.inheritedWindow : field === 'sequentialTuningParameter' ? C.automaticTuning : undefined,
128
- type: "number",
129
- min: min,
53
+ /*#__PURE__*/ _jsx(SelectEdit, {
54
+ label: C.exposurePolicy.label,
55
+ value: doc.exposurePolicy,
56
+ reason: reasonFor(test, 'exposurePolicy'),
57
+ options: exposureOptions(doc.kind).map((value)=>({
58
+ value,
59
+ label: C.policies[value]
60
+ })),
61
+ onCommit: (value)=>save(test, 'exposurePolicy', ()=>({
62
+ exposurePolicy: value
63
+ }))
64
+ }),
65
+ /*#__PURE__*/ _jsx("p", {
66
+ children: C.exposurePolicy.help
67
+ }),
68
+ /*#__PURE__*/ _jsx(FieldErrors, {
69
+ test: test,
70
+ path: "exposurePolicy"
71
+ })
72
+ ]
73
+ }),
74
+ surface.isAdmin ? /*#__PURE__*/ _jsxs("div", {
75
+ children: [
76
+ /*#__PURE__*/ _jsx(SelectEdit, {
77
+ label: C.method.label,
78
+ value: doc.analysis?.method ?? 'sequential',
130
79
  reason: reason,
131
- validate: field === 'minExposuresPerArm' ? positiveInteger : undefined,
132
- onCommit: (raw)=>change(field, raw.trim() ? Number(raw) : null)
80
+ options: [
81
+ {
82
+ value: 'sequential',
83
+ label: C.sequential
84
+ },
85
+ {
86
+ value: 'bayesian',
87
+ label: C.bayesian
88
+ }
89
+ ],
90
+ onCommit: (value)=>change('method', value)
133
91
  }),
134
92
  /*#__PURE__*/ _jsx("p", {
135
- children: C[field].help
93
+ children: C.method.help
136
94
  }),
137
95
  /*#__PURE__*/ _jsx(FieldErrors, {
138
96
  test: test,
139
- path: `analysis.${field}`
97
+ path: "analysis.method"
140
98
  })
141
99
  ]
142
- }, field)),
143
- readOnlyFields.map((field)=>/*#__PURE__*/ _jsxs("div", {
100
+ }) : null,
101
+ /*#__PURE__*/ _jsxs("div", {
144
102
  children: [
145
- /*#__PURE__*/ _jsx(ReadOnly, {
146
- label: C[field].label,
147
- value: field === 'slug' ? /*#__PURE__*/ _jsx("code", {
148
- children: String(doc.slug)
149
- }) : typeof doc[field] === 'string' || typeof doc[field] === 'number' ? String(doc[field]) : C.none,
150
- reason: reasonFor(test, field)
103
+ /*#__PURE__*/ _jsx(SelectEdit, {
104
+ label: C.cuped.label,
105
+ value: doc.analysis?.cuped ?? 'auto',
106
+ reason: reason,
107
+ options: [
108
+ {
109
+ value: 'auto',
110
+ label: C.auto
111
+ },
112
+ {
113
+ value: 'off',
114
+ label: C.off
115
+ }
116
+ ],
117
+ onCommit: (value)=>change('cuped', value)
151
118
  }),
152
119
  /*#__PURE__*/ _jsx("p", {
153
- children: C[field].help
120
+ children: C.cuped.help
121
+ }),
122
+ /*#__PURE__*/ _jsx(FieldErrors, {
123
+ test: test,
124
+ path: "analysis.cuped"
154
125
  })
155
126
  ]
156
- }, field))
157
- ]
158
- })
127
+ }),
128
+ numbers.map(([field, fallback, min])=>/*#__PURE__*/ _jsxs("div", {
129
+ children: [
130
+ /*#__PURE__*/ _jsx(TextEdit, {
131
+ label: C[field].label,
132
+ value: doc.analysis?.[field] ?? fallback,
133
+ placeholder: field === 'attributionWindowHours' ? C.inheritedWindow : field === 'sequentialTuningParameter' ? C.automaticTuning : undefined,
134
+ type: "number",
135
+ min: min,
136
+ reason: reason,
137
+ isValid: field === 'minExposuresPerArm' ? positiveInteger : undefined,
138
+ onCommit: (raw)=>change(field, raw.trim() ? Number(raw) : null)
139
+ }),
140
+ /*#__PURE__*/ _jsx("p", {
141
+ children: C[field].help
142
+ }),
143
+ /*#__PURE__*/ _jsx(FieldErrors, {
144
+ test: test,
145
+ path: `analysis.${field}`
146
+ })
147
+ ]
148
+ }, field)),
149
+ readOnlyFields.map((field)=>/*#__PURE__*/ _jsxs("div", {
150
+ children: [
151
+ /*#__PURE__*/ _jsx(ReadOnly, {
152
+ label: C[field].label,
153
+ value: field === 'slug' ? /*#__PURE__*/ _jsx("code", {
154
+ children: String(doc.slug)
155
+ }) : typeof doc[field] === 'string' || typeof doc[field] === 'number' ? String(doc[field]) : C.none,
156
+ reason: reasonFor(test, field)
157
+ }),
158
+ /*#__PURE__*/ _jsx("p", {
159
+ children: C[field].help
160
+ })
161
+ ]
162
+ }, field))
163
+ ]
164
+ })
165
+ ]
159
166
  });
160
167
  }
@@ -2,14 +2,21 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { fetchAllDocs } from '../../../lib/fetchAllDocs.js';
4
4
  import { Button } from '../../../ui/Button.js';
5
- import { useEffect, useState } from 'react';
6
- import { formatAdminURL } from 'payload/shared';
5
+ import { useEffect, useMemo, useState } from 'react';
6
+ import { surfaceUrls } from '../../surfaceUrls.js';
7
7
  import { TEST_PAGE as C } from '../../copy/testPage.js';
8
- import { metricSourceOptions } from '../../metricSource.model.js';
8
+ import { METRIC_KIND_LABELS } from '../../copy/metrics.js';
9
+ import { isClickSource } from '../../../goals/key.js';
10
+ import { createApi } from '../../api.js';
11
+ import { sourceFor } from '../../metricSource.model.js';
9
12
  import { reviewView } from '../../review/review.model.js';
10
13
  import { CheckboxList, FieldErrors, reasonFor, save, SelectEdit } from './fields.js';
11
14
  import { Section } from './Section.js';
12
15
  export function GoalSection({ test, surface }) {
16
+ const api = useMemo(()=>createApi(surface.apiRoute), [
17
+ surface.apiRoute
18
+ ]);
19
+ const [clickKeys, setClickKeys] = useState([]);
13
20
  const [metrics, setMetrics] = useState([]);
14
21
  const [failed, setFailed] = useState(false);
15
22
  const [loaded, setLoaded] = useState(false);
@@ -40,6 +47,40 @@ export function GoalSection({ test, surface }) {
40
47
  attempt
41
48
  ]);
42
49
  const doc = test.doc;
50
+ useEffect(()=>{
51
+ if (!surface.canManage) {
52
+ setClickKeys([]);
53
+ return;
54
+ }
55
+ let active = true;
56
+ let lastReturn = -Infinity;
57
+ let sequence = 0;
58
+ async function loadKeys() {
59
+ const request = ++sequence;
60
+ const result = await api.clickKeys(doc.id);
61
+ if (active && request === sequence) setClickKeys(result.ok && Array.isArray(result.data?.keys) ? result.data.keys : []);
62
+ }
63
+ const focus = ()=>{
64
+ if (document.visibilityState !== 'visible' || Date.now() - lastReturn < 100) return;
65
+ lastReturn = Date.now();
66
+ void loadKeys();
67
+ };
68
+ const visible = ()=>{
69
+ if (document.visibilityState === 'visible') focus();
70
+ };
71
+ void loadKeys();
72
+ window.addEventListener('focus', focus);
73
+ document.addEventListener('visibilitychange', visible);
74
+ return ()=>{
75
+ active = false;
76
+ window.removeEventListener('focus', focus);
77
+ document.removeEventListener('visibilitychange', visible);
78
+ };
79
+ }, [
80
+ api,
81
+ doc.id,
82
+ surface.canManage
83
+ ]);
43
84
  const reason = reasonFor(test, 'metrics');
44
85
  useEffect(()=>{
45
86
  if (!loaded) return;
@@ -66,12 +107,23 @@ export function GoalSection({ test, surface }) {
66
107
  doc.metrics,
67
108
  test
68
109
  ]);
69
- const sources = metricSourceOptions(surface.metricSources);
70
110
  const unknownGoal = loaded ? C.missingGoal : C.goalUnavailable;
71
- const options = metrics.map((metric)=>({
111
+ const options = metrics.map((metric)=>{
112
+ const kind = sourceFor(metric.source, surface.metricSources)?.kind;
113
+ return {
72
114
  value: String(metric.id),
73
- label: `${metric.name} · ${sources.find((source)=>source.value === metric.source)?.badge ?? metric.source}`
74
- }));
115
+ label: `${metric.name} · ${kind ? METRIC_KIND_LABELS[kind] : metric.source}`
116
+ };
117
+ });
118
+ const onPage = new Map(metrics.flatMap((metric)=>{
119
+ const found = isClickSource(metric.source) ? clickKeys.find((row)=>row.key === metric.source.slice(6)) : undefined;
120
+ return found ? [
121
+ [
122
+ String(metric.id),
123
+ `${metric.name} · ${found.label}`
124
+ ]
125
+ ] : [];
126
+ }));
75
127
  const chosen = [
76
128
  doc.metrics?.primary,
77
129
  ...doc.metrics?.guardrails ?? [],
@@ -94,6 +146,18 @@ export function GoalSection({ test, surface }) {
94
146
  ].map(String));
95
147
  return visible.filter((option)=>!blocked.has(option.value));
96
148
  };
149
+ const roleGroups = (role)=>onPage.size ? [
150
+ {
151
+ label: C.clickPageGroup,
152
+ options: roleOptions(role).filter((option)=>onPage.has(option.value)).map((option)=>({
153
+ ...option,
154
+ label: onPage.get(option.value)
155
+ }))
156
+ },
157
+ {
158
+ options: roleOptions(role).filter((option)=>!onPage.has(option.value))
159
+ }
160
+ ] : undefined;
97
161
  const originalId = (value)=>metrics.find((metric)=>String(metric.id) === value)?.id ?? [
98
162
  test.doc.metrics?.primary,
99
163
  ...test.doc.metrics?.guardrails ?? [],
@@ -125,7 +189,7 @@ export function GoalSection({ test, surface }) {
125
189
  });
126
190
  const display = (ids)=>ids.length ? ids.map((id)=>options.find((option)=>option.value === String(id))?.label ?? unknownGoal).join(', ') : C.noGoal;
127
191
  const primary = metrics.find((metric)=>String(metric.id) === String(doc.metrics?.primary));
128
- const kind = surface.metricSources.find((source)=>source.id === primary?.source)?.kind;
192
+ const kind = sourceFor(primary?.source, surface.metricSources)?.kind;
129
193
  const baseline = test.preflight?.baseline && String(test.preflight.baseline.metricId) === String(doc.metrics?.primary) ? reviewView(test.preflight, {
130
194
  primaryMetricKind: kind
131
195
  }).baseline : null;
@@ -153,12 +217,9 @@ export function GoalSection({ test, surface }) {
153
217
  children: /*#__PURE__*/ _jsx(Button, {
154
218
  variant: "link",
155
219
  size: "sm",
156
- href: formatAdminURL({
157
- adminRoute: surface.adminRoute,
158
- path: `/experiments/goals${!reason ? `?${new URLSearchParams({
159
- return: String(doc.id)
160
- })}` : ''}`
161
- }),
220
+ href: surfaceUrls.goals(surface.adminRoute, !reason ? {
221
+ return: String(doc.id)
222
+ } : undefined),
162
223
  children: C.manageGoals
163
224
  })
164
225
  }),
@@ -171,6 +232,17 @@ export function GoalSection({ test, surface }) {
171
232
  },
172
233
  ...roleOptions('primary')
173
234
  ],
235
+ groups: onPage.size ? [
236
+ {
237
+ options: [
238
+ {
239
+ value: '',
240
+ label: C.chooseGoal
241
+ }
242
+ ]
243
+ },
244
+ ...roleGroups('primary')
245
+ ] : undefined,
174
246
  reason: reason,
175
247
  readOnlyValue: display(doc.metrics?.primary == null ? [] : [
176
248
  doc.metrics.primary
@@ -180,6 +252,9 @@ export function GoalSection({ test, surface }) {
180
252
  /*#__PURE__*/ _jsx("p", {
181
253
  children: C.goalHelp
182
254
  }),
255
+ primary && isClickSource(primary.source) ? /*#__PURE__*/ _jsx("p", {
256
+ children: C.clickGoalHelp
257
+ }) : null,
183
258
  baseline ? /*#__PURE__*/ _jsxs("p", {
184
259
  children: [
185
260
  primary?.name ? `${primary.name}: ` : '',
@@ -195,6 +270,7 @@ export function GoalSection({ test, surface }) {
195
270
  label: C.guardrails,
196
271
  value: (doc.metrics?.guardrails ?? []).map(String),
197
272
  options: roleOptions('guardrails'),
273
+ groups: roleGroups('guardrails'),
198
274
  reason: reason,
199
275
  readOnlyValue: display(doc.metrics?.guardrails ?? []),
200
276
  onToggle: (value, checked)=>toggle('guardrails', value, checked)
@@ -211,6 +287,7 @@ export function GoalSection({ test, surface }) {
211
287
  label: C.secondary,
212
288
  value: (doc.metrics?.secondary ?? []).map(String),
213
289
  options: roleOptions('secondary'),
290
+ groups: roleGroups('secondary'),
214
291
  reason: reason,
215
292
  readOnlyValue: display(doc.metrics?.secondary ?? []),
216
293
  onToggle: (value, checked)=>toggle('secondary', value, checked)
@@ -219,6 +296,9 @@ export function GoalSection({ test, surface }) {
219
296
  test: test,
220
297
  path: "metrics.secondary"
221
298
  }),
299
+ metrics.some((metric)=>isClickSource(metric.source)) ? /*#__PURE__*/ _jsx("p", {
300
+ children: C.clickGoalsNote
301
+ }) : null,
222
302
  test.errors.metrics ? /*#__PURE__*/ _jsx("p", {
223
303
  role: "alert",
224
304
  children: test.errors.metrics
@@ -55,7 +55,7 @@ export function LengthSection({ test }) {
55
55
  type: "number",
56
56
  min: 1,
57
57
  reason: reason,
58
- validate: positiveInteger,
58
+ isValid: positiveInteger,
59
59
  onCommit: (value)=>change('plannedDurationDays', value),
60
60
  help: weeksHint(doc.analysis?.plannedDurationDays ?? 28)
61
61
  }),
@@ -0,0 +1,3 @@
1
+ import type { SectionProps } from '../contracts.js';
2
+ /** Whole-page holdouts have no page of their own; their controls live under Advanced. */
3
+ export declare function PageHoldout({ test }: SectionProps): import("react").JSX.Element;
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { TEST_PAGE as C } from '../../copy/testPage.js';
4
+ import { ARMS_COPY } from '../../copy/arms.js';
5
+ import { addHoldoutArm, removeArm, splitEvenly } from '../../arms/arms.model.js';
6
+ import { balanceWeight } from './testPage.model.js';
7
+ import { FieldErrors, reasonFor, save, TextEdit } from './fields.js';
8
+ import { Button } from '../../../ui/Button.js';
9
+ /** Whole-page holdouts have no page of their own; their controls live under Advanced. */ export function PageHoldout({ test }) {
10
+ const doc = test.doc;
11
+ const arm = doc.arms.find((item)=>item.kind === 'holdout');
12
+ const index = doc.arms.findIndex((item)=>item.kind === 'holdout');
13
+ const reason = reasonFor(test, 'arms');
14
+ return /*#__PURE__*/ _jsxs("div", {
15
+ children: [
16
+ arm ? /*#__PURE__*/ _jsxs(_Fragment, {
17
+ children: [
18
+ /*#__PURE__*/ _jsx(TextEdit, {
19
+ label: `${C.holdoutPrefix}${C.label}`,
20
+ value: arm.label,
21
+ reason: reason,
22
+ onCommit: (label)=>save(test, 'arms', ()=>({
23
+ arms: doc.arms.map((item)=>item.key === arm.key ? {
24
+ ...item,
25
+ label
26
+ } : item)
27
+ }))
28
+ }),
29
+ /*#__PURE__*/ _jsx(TextEdit, {
30
+ label: `${C.holdoutPrefix}${C.weight}`,
31
+ type: "number",
32
+ min: 0,
33
+ max: 100,
34
+ value: arm.weight,
35
+ reason: reason,
36
+ isValid: (value)=>value.trim() !== '' && Number.isInteger(Number(value)) && Number(value) >= 0 && Number(value) <= 100,
37
+ invalidMessage: C.invalidShare,
38
+ onCommit: (value)=>save(test, 'arms', ()=>({
39
+ arms: balanceWeight(doc.arms, arm.key, Number(value))
40
+ }))
41
+ }),
42
+ /*#__PURE__*/ _jsx(FieldErrors, {
43
+ test: test,
44
+ path: `arms.${index}`
45
+ }),
46
+ /*#__PURE__*/ _jsx(Button, {
47
+ disabled: !!reason,
48
+ onClick: ()=>save(test, 'arms', ()=>({
49
+ arms: splitEvenly(removeArm(doc.arms, arm.key))
50
+ })),
51
+ children: C.remove
52
+ })
53
+ ]
54
+ }) : /*#__PURE__*/ _jsx(Button, {
55
+ disabled: !!reason,
56
+ onClick: ()=>save(test, 'arms', ()=>({
57
+ arms: addHoldoutArm(doc.arms)
58
+ })),
59
+ children: C.addHoldout
60
+ }),
61
+ /*#__PURE__*/ _jsx("p", {
62
+ className: "xp-ui-help",
63
+ children: ARMS_COPY.holdout
64
+ })
65
+ ]
66
+ });
67
+ }
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { surfaceUrls } from '../../surfaceUrls.js';
3
4
  import { useEffect, useState } from 'react';
4
5
  import { formatAdminURL } from 'payload/shared';
5
6
  import { useTestDocument } from '../useTestDocument.js';
@@ -23,7 +24,8 @@ import './testPage.css';
23
24
  export function TestPage({ id, surface, onTitleChange }) {
24
25
  const [barHeight, setBarHeight] = useState();
25
26
  const test = useTestDocument(id, {
26
- apiRoute: surface.apiRoute
27
+ apiRoute: surface.apiRoute,
28
+ refetchPreflightOnFocus: true
27
29
  });
28
30
  useEffect(()=>{
29
31
  if (test.doc?.name) onTitleChange?.(test.doc.name);
@@ -37,10 +39,7 @@ export function TestPage({ id, surface, onTitleChange }) {
37
39
  crumbs: [
38
40
  {
39
41
  label: C.tests,
40
- href: formatAdminURL({
41
- adminRoute: surface.adminRoute,
42
- path: '/experiments'
43
- })
42
+ href: surfaceUrls.home(surface.adminRoute)
44
43
  },
45
44
  {
46
45
  label: C.test
@@ -71,10 +70,7 @@ export function TestPage({ id, surface, onTitleChange }) {
71
70
  crumbs: [
72
71
  {
73
72
  label: C.tests,
74
- href: formatAdminURL({
75
- adminRoute: surface.adminRoute,
76
- path: '/experiments'
77
- })
73
+ href: surfaceUrls.home(surface.adminRoute)
78
74
  },
79
75
  {
80
76
  label: test.doc.name
@@ -10,6 +10,8 @@ import { addHoldoutArm, addVariant, issuesByRow, removeArm, splitEvenly, totalWe
10
10
  import { allocationSentence } from '../../allocation.model.js';
11
11
  import { balanceWeight, orderedVersions, trafficShares } from './testPage.model.js';
12
12
  import { FieldErrors, ReadOnly, reasonFor, save, TextEdit } from './fields.js';
13
+ import { PAGE_COPY } from '../../copy/pages.js';
14
+ import { testPageCollection } from '../pagePicker/pagePicker.model.js';
13
15
  import { Section } from './Section.js';
14
16
  const COLOURS = {
15
17
  control: 'var(--xp-series-muted)',
@@ -49,6 +51,9 @@ export function VersionsSection({ test, surface, parts }) {
49
51
  test: test,
50
52
  sectionId: "versions",
51
53
  children: [
54
+ doc.kind === 'page' && testPageCollection(surface.pageCollections, doc, test.preflight?.pagePlacements)?.contentChecks === false ? /*#__PURE__*/ _jsx("p", {
55
+ children: PAGE_COPY.contentChecksOff
56
+ }) : null,
52
57
  reason ? /*#__PURE__*/ _jsx("p", {
53
58
  className: "xp-ui-help",
54
59
  children: reason
@@ -70,8 +75,10 @@ export function VersionsSection({ test, surface, parts }) {
70
75
  /*#__PURE__*/ _jsx("span", {})
71
76
  ]
72
77
  }),
73
- ordered.map(({ arm, index })=>/*#__PURE__*/ _jsxs("article", {
78
+ ordered.filter(({ arm })=>doc.kind !== 'page' || arm.kind !== 'holdout').map(({ arm, index })=>/*#__PURE__*/ _jsxs("article", {
74
79
  className: "xp-version-row",
80
+ id: `xp-version-${arm.key}`,
81
+ tabIndex: -1,
75
82
  children: [
76
83
  /*#__PURE__*/ _jsx(TextEdit, {
77
84
  label: `${GROUP_LABELS[arm.key]}: ${C.label}`,
@@ -93,7 +100,7 @@ export function VersionsSection({ test, surface, parts }) {
93
100
  reason: reason,
94
101
  hideLabel: true,
95
102
  disabled: Boolean(reason),
96
- validate: (raw)=>raw.trim() !== '' && Number.isInteger(Number(raw)) && Number(raw) >= 0 && Number(raw) <= 100,
103
+ isValid: (raw)=>raw.trim() !== '' && Number.isInteger(Number(raw)) && Number(raw) >= 0 && Number(raw) <= 100,
97
104
  invalidMessage: C.invalidShare,
98
105
  onDraft: (value)=>setWeightEdit({
99
106
  key: arm.key,
@@ -177,11 +184,11 @@ export function VersionsSection({ test, surface, parts }) {
177
184
  onClick: ()=>update((arms)=>addVariant(arms, doc.kind)),
178
185
  children: C.addVersion
179
186
  }),
180
- /*#__PURE__*/ _jsx(Button, {
187
+ doc.kind !== 'page' ? /*#__PURE__*/ _jsx(Button, {
181
188
  disabled: Boolean(reason) || doc.arms.some((arm)=>arm.kind === 'holdout'),
182
189
  onClick: ()=>update(addHoldoutArm),
183
190
  children: C.addHoldout
184
- }),
191
+ }) : null,
185
192
  /*#__PURE__*/ _jsx(Button, {
186
193
  disabled: Boolean(reason),
187
194
  onClick: ()=>update(splitEvenly),
@@ -189,7 +196,7 @@ export function VersionsSection({ test, surface, parts }) {
189
196
  })
190
197
  ]
191
198
  }),
192
- doc.arms.some((arm)=>arm.kind === 'holdout') ? /*#__PURE__*/ _jsx("p", {
199
+ doc.kind !== 'page' && doc.arms.some((arm)=>arm.kind === 'holdout') ? /*#__PURE__*/ _jsx("p", {
193
200
  className: "xp-ui-help",
194
201
  children: ARMS_COPY.holdout
195
202
  }) : null,