@delmaredigital/payload-experiments 0.3.0-rc.4 → 0.4.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/README.md +113 -20
  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/actions/ConfirmDialog.d.ts +2 -1
  8. package/dist/admin/actions/ConfirmDialog.js +2 -2
  9. package/dist/admin/actions/DecideDialog.d.ts +2 -1
  10. package/dist/admin/actions/DecideDialog.js +2 -2
  11. package/dist/admin/api.d.ts +11 -2
  12. package/dist/admin/api.js +20 -2
  13. package/dist/admin/blocks/BlockPanel.d.ts +4 -0
  14. package/dist/admin/blocks/BlockPanel.js +153 -0
  15. package/dist/admin/blocks/TestBlockField.d.ts +5 -0
  16. package/dist/admin/blocks/TestBlockField.js +163 -0
  17. package/dist/admin/blocks/formApi.d.ts +43 -0
  18. package/dist/admin/blocks/formApi.js +153 -0
  19. package/dist/admin/blocks/inject.d.ts +7 -0
  20. package/dist/admin/blocks/inject.js +41 -0
  21. package/dist/admin/blocks/model.d.ts +17 -0
  22. package/dist/admin/blocks/model.js +45 -0
  23. package/dist/admin/blocks/statusCache.d.ts +7 -0
  24. package/dist/admin/blocks/statusCache.js +83 -0
  25. package/dist/admin/blocks/useBlockForm.d.ts +17 -0
  26. package/dist/admin/blocks/useBlockForm.js +46 -0
  27. package/dist/admin/copy/actions.d.ts +2 -0
  28. package/dist/admin/copy/actions.js +9 -0
  29. package/dist/admin/copy/blocks.d.ts +27 -0
  30. package/dist/admin/copy/blocks.js +38 -0
  31. package/dist/admin/copy/goals.d.ts +2 -0
  32. package/dist/admin/copy/goals.js +4 -1
  33. package/dist/admin/copy/newTest.d.ts +10 -0
  34. package/dist/admin/copy/newTest.js +10 -0
  35. package/dist/admin/copy/pages.d.ts +26 -0
  36. package/dist/admin/copy/pages.js +26 -0
  37. package/dist/admin/copy/readiness.d.ts +22 -3
  38. package/dist/admin/copy/readiness.js +23 -3
  39. package/dist/admin/copy/results.d.ts +1 -0
  40. package/dist/admin/copy/results.js +1 -0
  41. package/dist/admin/copy/testPage.d.ts +4 -0
  42. package/dist/admin/copy/testPage.js +4 -0
  43. package/dist/admin/goals/XpGoalField.d.ts +2 -0
  44. package/dist/admin/goals/XpGoalField.js +152 -0
  45. package/dist/admin/metricSource.model.d.ts +2 -0
  46. package/dist/admin/metricSource.model.js +4 -0
  47. package/dist/admin/pageCollections.d.ts +10 -0
  48. package/dist/admin/pageCollections.js +20 -0
  49. package/dist/admin/paths.d.ts +6 -0
  50. package/dist/admin/paths.js +6 -0
  51. package/dist/admin/placementStatus.model.d.ts +2 -0
  52. package/dist/admin/placementStatus.model.js +19 -0
  53. package/dist/admin/placements/placementLabels.d.ts +11 -0
  54. package/dist/admin/placements/placementLabels.js +21 -0
  55. package/dist/admin/placements/placementLabels.model.d.ts +13 -0
  56. package/dist/admin/placements/placementLabels.model.js +12 -0
  57. package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
  58. package/dist/admin/placements/usePlacementLabels.js +102 -0
  59. package/dist/admin/results/DataQualityPanel.js +4 -1
  60. package/dist/admin/results/results.model.d.ts +3 -0
  61. package/dist/admin/results/results.model.js +8 -4
  62. package/dist/admin/surface/TestsHome.js +37 -6
  63. package/dist/admin/surface/TestsSurface.js +5 -1
  64. package/dist/admin/surface/contracts.d.ts +6 -5
  65. package/dist/admin/surface/goals/GoalForm.js +67 -34
  66. package/dist/admin/surface/goals/GoalsList.js +3 -3
  67. package/dist/admin/surface/goals/data.js +4 -1
  68. package/dist/admin/surface/goals/goals.model.d.ts +1 -2
  69. package/dist/admin/surface/goals/goals.model.js +1 -3
  70. package/dist/admin/surface/home.model.d.ts +1 -1
  71. package/dist/admin/surface/home.model.js +8 -4
  72. package/dist/admin/surface/newTest/NewTest.js +54 -13
  73. package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
  74. package/dist/admin/surface/newTest/PageWizard.js +209 -0
  75. package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
  76. package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
  77. package/dist/admin/surface/pagePicker/PagePicker.d.ts +10 -0
  78. package/dist/admin/surface/pagePicker/PagePicker.js +172 -0
  79. package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +25 -0
  80. package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
  81. package/dist/admin/surface/readiness/ActionDialogs.js +99 -64
  82. package/dist/admin/surface/readiness/Banner.js +30 -6
  83. package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
  84. package/dist/admin/surface/readiness/readiness.model.js +91 -9
  85. package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
  86. package/dist/admin/surface/results/ReleasePages.js +3 -7
  87. package/dist/admin/surface/results/ResultsBody.js +21 -2
  88. package/dist/admin/surface/results/ResultsHeader.js +2 -5
  89. package/dist/admin/surface/results/useResults.d.ts +10 -1
  90. package/dist/admin/surface/test/AdvancedSection.js +111 -104
  91. package/dist/admin/surface/test/GoalSection.js +90 -7
  92. package/dist/admin/surface/test/LengthSection.js +1 -1
  93. package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
  94. package/dist/admin/surface/test/PageHoldout.js +67 -0
  95. package/dist/admin/surface/test/TestPage.js +2 -1
  96. package/dist/admin/surface/test/VersionsSection.js +12 -5
  97. package/dist/admin/surface/test/fields.d.ts +13 -4
  98. package/dist/admin/surface/test/fields.js +46 -14
  99. package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
  100. package/dist/admin/surface/test/jumpToSection.js +4 -3
  101. package/dist/admin/surface/test/pageParts.d.ts +4 -1
  102. package/dist/admin/surface/test/pageParts.js +79 -2
  103. package/dist/admin/surface/test/testPage.css +8 -1
  104. package/dist/admin/surface/testDocument.model.d.ts +1 -0
  105. package/dist/admin/surface/testDocument.model.js +2 -1
  106. package/dist/admin/surface/useTestDocument.d.ts +11 -2
  107. package/dist/admin/surface/useTestDocument.js +53 -15
  108. package/dist/admin/useApi.d.ts +11 -2
  109. package/dist/admin/useApi.js +3 -5
  110. package/dist/admin/views/ExperimentsRootView.js +58 -16
  111. package/dist/blocks/bindings.d.ts +3 -0
  112. package/dist/blocks/bindings.js +34 -0
  113. package/dist/blocks/createExperimentBlock.d.ts +6 -0
  114. package/dist/blocks/createExperimentBlock.js +77 -0
  115. package/dist/blocks/formState.d.ts +31 -0
  116. package/dist/blocks/formState.js +146 -0
  117. package/dist/blocks/model.d.ts +41 -0
  118. package/dist/blocks/model.js +34 -0
  119. package/dist/client/ExperimentGroup.d.ts +6 -0
  120. package/dist/client/ExperimentGroup.js +9 -0
  121. package/dist/client/ExperimentsProvider.js +20 -4
  122. package/dist/client/clickGoals.d.ts +4 -0
  123. package/dist/client/clickGoals.js +43 -0
  124. package/dist/collections/experiments/config.js +12 -2
  125. package/dist/collections/experiments/deleteGuard.d.ts +2 -1
  126. package/dist/collections/experiments/deleteGuard.js +12 -1
  127. package/dist/collections/experiments/editability.js +4 -2
  128. package/dist/collections/experiments/pickSlug.d.ts +2 -0
  129. package/dist/collections/experiments/pickSlug.js +8 -0
  130. package/dist/collections/labels.d.ts +3 -0
  131. package/dist/collections/labels.js +20 -0
  132. package/dist/collections/metrics.js +12 -2
  133. package/dist/collections/phases.js +1 -0
  134. package/dist/collections/serialize.js +7 -0
  135. package/dist/db/clickGoals.d.ts +9 -0
  136. package/dist/db/clickGoals.js +22 -0
  137. package/dist/db/delete.d.ts +1 -0
  138. package/dist/db/delete.js +1 -0
  139. package/dist/db/locks.d.ts +8 -0
  140. package/dist/db/locks.js +27 -4
  141. package/dist/db/names.d.ts +4 -0
  142. package/dist/db/names.js +4 -0
  143. package/dist/db/pageBindings.d.ts +4 -0
  144. package/dist/db/pageBindings.js +17 -0
  145. package/dist/db/pageBusy.d.ts +2 -0
  146. package/dist/db/pageBusy.js +10 -0
  147. package/dist/db/pageContent.d.ts +10 -0
  148. package/dist/db/pageContent.js +79 -0
  149. package/dist/db/placements.d.ts +33 -0
  150. package/dist/db/placements.js +54 -0
  151. package/dist/db/slugs.d.ts +8 -0
  152. package/dist/db/slugs.js +40 -0
  153. package/dist/db/snapshots.d.ts +6 -0
  154. package/dist/db/snapshots.js +8 -5
  155. package/dist/endpoints/clickKeys.d.ts +5 -0
  156. package/dist/endpoints/clickKeys.js +17 -0
  157. package/dist/endpoints/events.js +4 -1
  158. package/dist/endpoints/index.js +28 -0
  159. package/dist/endpoints/pages.d.ts +5 -0
  160. package/dist/endpoints/pages.js +120 -0
  161. package/dist/endpoints/placementStatus.d.ts +6 -0
  162. package/dist/endpoints/placementStatus.js +238 -0
  163. package/dist/endpoints/readBody.d.ts +2 -0
  164. package/dist/endpoints/readBody.js +24 -0
  165. package/dist/endpoints/results.js +5 -1
  166. package/dist/endpoints/summary.js +11 -4
  167. package/dist/endpoints/wrapBlock.d.ts +6 -0
  168. package/dist/endpoints/wrapBlock.js +76 -0
  169. package/dist/errors.d.ts +3 -3
  170. package/dist/errors.js +4 -4
  171. package/dist/events/rows.d.ts +1 -1
  172. package/dist/events/rows.js +5 -3
  173. package/dist/exports/adminClient.d.ts +5 -0
  174. package/dist/exports/adminClient.js +5 -0
  175. package/dist/exports/blocks.d.ts +2 -0
  176. package/dist/exports/blocks.js +1 -0
  177. package/dist/exports/client.d.ts +1 -0
  178. package/dist/exports/client.js +1 -0
  179. package/dist/exports/index.d.ts +6 -1
  180. package/dist/exports/index.js +3 -0
  181. package/dist/exports/puck.d.ts +3 -1
  182. package/dist/exports/puck.js +2 -0
  183. package/dist/exports/puckEditor.d.ts +3 -0
  184. package/dist/exports/puckEditor.js +2 -0
  185. package/dist/exports/rsc.d.ts +3 -0
  186. package/dist/exports/rsc.js +2 -0
  187. package/dist/fields/xpGoal.d.ts +3 -0
  188. package/dist/fields/xpGoal.js +28 -0
  189. package/dist/globals/state.js +10 -0
  190. package/dist/goals/cache.d.ts +3 -0
  191. package/dist/goals/cache.js +35 -0
  192. package/dist/goals/fairness.d.ts +40 -0
  193. package/dist/goals/fairness.js +200 -0
  194. package/dist/goals/key.d.ts +4 -0
  195. package/dist/goals/key.js +3 -0
  196. package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
  197. package/dist/goals/picker/clickGoalPicker.model.js +45 -0
  198. package/dist/goals/picker/copy.d.ts +16 -0
  199. package/dist/goals/picker/copy.js +15 -0
  200. package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
  201. package/dist/goals/picker/useClickGoalPicker.js +220 -0
  202. package/dist/hooks/pageGuard.d.ts +5 -1
  203. package/dist/hooks/pageGuard.js +157 -95
  204. package/dist/onInit.js +29 -0
  205. package/dist/options.d.ts +2 -0
  206. package/dist/options.js +41 -1
  207. package/dist/plugin.js +41 -28
  208. package/dist/puck/copy.d.ts +1 -0
  209. package/dist/puck/copy.js +1 -0
  210. package/dist/puck/editor/editor.chrome.css +1 -0
  211. package/dist/puck/editor/editor.css +1 -0
  212. package/dist/puck/editor/editor.model.d.ts +4 -2
  213. package/dist/puck/editor/editor.model.js +6 -3
  214. package/dist/puck/editor/integration.d.ts +10 -0
  215. package/dist/puck/editor/integration.js +30 -0
  216. package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
  217. package/dist/puck/editor/xpGoalPuckEditorField.js +134 -0
  218. package/dist/puck/integration.d.ts +6 -0
  219. package/dist/puck/integration.js +21 -0
  220. package/dist/puck/xpGoalField.d.ts +3 -0
  221. package/dist/puck/xpGoalField.js +6 -0
  222. package/dist/results/health.d.ts +4 -0
  223. package/dist/results/health.js +27 -0
  224. package/dist/results/snapshot.js +15 -2
  225. package/dist/sanitizeReadinessDetail.js +41 -0
  226. package/dist/server/pageKindSeoContract.d.ts +27 -0
  227. package/dist/server/pageKindSeoContract.js +80 -0
  228. package/dist/server/recordContentChange.js +58 -8
  229. package/dist/server/resolveExperimentBlocks.d.ts +5 -0
  230. package/dist/server/resolveExperimentBlocks.js +54 -0
  231. package/dist/server/runResults.js +56 -15
  232. package/dist/server/toClientAssignments.js +2 -1
  233. package/dist/transitions/addMetric.d.ts +2 -1
  234. package/dist/transitions/addMetric.js +28 -1
  235. package/dist/transitions/checkStart.d.ts +13 -1
  236. package/dist/transitions/checkStart.js +89 -7
  237. package/dist/transitions/decide.js +24 -0
  238. package/dist/transitions/deleteExperiment.d.ts +3 -1
  239. package/dist/transitions/deleteExperiment.js +27 -1
  240. package/dist/transitions/kernel.js +4 -0
  241. package/dist/transitions/makeContent.d.ts +5 -0
  242. package/dist/transitions/makeContent.js +39 -11
  243. package/dist/transitions/pageCollection.d.ts +10 -0
  244. package/dist/transitions/pageCollection.js +20 -0
  245. package/dist/transitions/pageVersions.d.ts +10 -0
  246. package/dist/transitions/pageVersions.js +44 -0
  247. package/dist/transitions/phaseRecord.d.ts +1 -0
  248. package/dist/transitions/placementInteractions.d.ts +40 -0
  249. package/dist/transitions/placementInteractions.js +94 -0
  250. package/dist/transitions/placements.d.ts +8 -0
  251. package/dist/transitions/placements.js +148 -1
  252. package/dist/transitions/preflightData.d.ts +2 -2
  253. package/dist/transitions/preflightData.js +137 -5
  254. package/dist/transitions/resolveCaps.d.ts +1 -0
  255. package/dist/transitions/resolveCaps.js +6 -2
  256. package/dist/transitions/start.js +45 -1
  257. package/dist/types/admin.d.ts +1 -1
  258. package/dist/types/assignments.d.ts +1 -0
  259. package/dist/types/contracts.d.ts +15 -0
  260. package/dist/types/endpoints.d.ts +92 -5
  261. package/dist/types/index.d.ts +3 -0
  262. package/dist/types/puck.d.ts +7 -1
  263. package/dist/types/snapshot.d.ts +17 -4
  264. package/dist/types/surface.d.ts +13 -0
  265. package/package.json +8 -1
@@ -29,6 +29,14 @@ export declare function useResults(id: DocId, { apiRoute, canReadResults, canMan
29
29
  canManage?: boolean;
30
30
  }): {
31
31
  api: {
32
+ wrapBlock: (body: import("../../../types/endpoints.js").WrapBlockBody) => Promise<ApiResult<Record<string, unknown>>>;
33
+ pages: (query: {
34
+ collection: string;
35
+ q?: string;
36
+ path?: string;
37
+ page?: number;
38
+ }, signal?: AbortSignal) => Promise<ApiResult<import("../../../types/endpoints.js").PagesResult>>;
39
+ placementStatus: (body: import("../../../types/endpoints.js").PlacementStatusBody) => Promise<ApiResult<import("../../../types/endpoints.js").PlacementStatusResult>>;
32
40
  start: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
33
41
  allocate: (id: DocId, body: import("../../../types/endpoints.js").AllocateBody, options?: {
34
42
  notFound?: string;
@@ -39,7 +47,7 @@ export declare function useResults(id: DocId, { apiRoute, canReadResults, canMan
39
47
  notFound?: string;
40
48
  codes?: Readonly<Record<string, string>>;
41
49
  fallback?: string;
42
- }) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
50
+ }) => Promise<ApiResult<import("../../../types/endpoints.js").AddMetricResult>>;
43
51
  stop: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
44
52
  decide: (id: DocId, body: import("../../../types/endpoints.js").DecideBody) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
45
53
  releaseHoldout: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
@@ -48,6 +56,7 @@ export declare function useResults(id: DocId, { apiRoute, canReadResults, canMan
48
56
  dismissContentEntry: (id: DocId, body: import("../../../types/endpoints.js").DismissContentEntryBody) => Promise<ApiResult<import("../../../types/endpoints.js").DismissContentEntryResult>>;
49
57
  restoreContentEntry: (id: DocId, body: import("../../../types/endpoints.js").RestoreContentEntryBody) => Promise<ApiResult<import("../../../types/endpoints.js").RestoreContentEntryResult>>;
50
58
  archive: (id: DocId, body: import("../../../types/endpoints.js").ArchiveBody) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
59
+ clickKeys: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").ClickKeysResult>>;
51
60
  preflight: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").PreflightResult>>;
52
61
  previewToken: (id: DocId, arm: string) => Promise<ApiResult<import("../../../types/endpoints.js").PreviewTokenResult>>;
53
62
  results: (id: DocId, options?: {
@@ -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';
5
+ import { useEffect, useMemo, useState } from 'react';
6
6
  import { formatAdminURL } from 'payload/shared';
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;
@@ -171,6 +235,17 @@ export function GoalSection({ test, surface }) {
171
235
  },
172
236
  ...roleOptions('primary')
173
237
  ],
238
+ groups: onPage.size ? [
239
+ {
240
+ options: [
241
+ {
242
+ value: '',
243
+ label: C.chooseGoal
244
+ }
245
+ ]
246
+ },
247
+ ...roleGroups('primary')
248
+ ] : undefined,
174
249
  reason: reason,
175
250
  readOnlyValue: display(doc.metrics?.primary == null ? [] : [
176
251
  doc.metrics.primary
@@ -180,6 +255,9 @@ export function GoalSection({ test, surface }) {
180
255
  /*#__PURE__*/ _jsx("p", {
181
256
  children: C.goalHelp
182
257
  }),
258
+ primary && isClickSource(primary.source) ? /*#__PURE__*/ _jsx("p", {
259
+ children: C.clickGoalHelp
260
+ }) : null,
183
261
  baseline ? /*#__PURE__*/ _jsxs("p", {
184
262
  children: [
185
263
  primary?.name ? `${primary.name}: ` : '',
@@ -195,6 +273,7 @@ export function GoalSection({ test, surface }) {
195
273
  label: C.guardrails,
196
274
  value: (doc.metrics?.guardrails ?? []).map(String),
197
275
  options: roleOptions('guardrails'),
276
+ groups: roleGroups('guardrails'),
198
277
  reason: reason,
199
278
  readOnlyValue: display(doc.metrics?.guardrails ?? []),
200
279
  onToggle: (value, checked)=>toggle('guardrails', value, checked)
@@ -211,6 +290,7 @@ export function GoalSection({ test, surface }) {
211
290
  label: C.secondary,
212
291
  value: (doc.metrics?.secondary ?? []).map(String),
213
292
  options: roleOptions('secondary'),
293
+ groups: roleGroups('secondary'),
214
294
  reason: reason,
215
295
  readOnlyValue: display(doc.metrics?.secondary ?? []),
216
296
  onToggle: (value, checked)=>toggle('secondary', value, checked)
@@ -219,6 +299,9 @@ export function GoalSection({ test, surface }) {
219
299
  test: test,
220
300
  path: "metrics.secondary"
221
301
  }),
302
+ metrics.some((metric)=>isClickSource(metric.source)) ? /*#__PURE__*/ _jsx("p", {
303
+ children: C.clickGoalsNote
304
+ }) : null,
222
305
  test.errors.metrics ? /*#__PURE__*/ _jsx("p", {
223
306
  role: "alert",
224
307
  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
+ }
@@ -23,7 +23,8 @@ import './testPage.css';
23
23
  export function TestPage({ id, surface, onTitleChange }) {
24
24
  const [barHeight, setBarHeight] = useState();
25
25
  const test = useTestDocument(id, {
26
- apiRoute: surface.apiRoute
26
+ apiRoute: surface.apiRoute,
27
+ refetchPreflightOnFocus: true
27
28
  });
28
29
  useEffect(()=>{
29
30
  if (test.doc?.name) onTitleChange?.(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,
@@ -15,7 +15,7 @@ export declare function ReadOnly({ label, value, reason, labelAction }: {
15
15
  labelAction?: ReactNode;
16
16
  }): import("react").JSX.Element;
17
17
  /** Local typing is intentionally separate from staging: build the current group on commit. */
18
- export declare function TextEdit({ label, value, onCommit, reason, multiline, placeholder, type, min, max, required, onDraft, validate, invalidMessage, hideLabel, disabled, help }: {
18
+ export declare function TextEdit({ label, value, onCommit, reason, multiline, placeholder, type, min, max, required, onDraft, isValid, invalidMessage, hideLabel, disabled, help }: {
19
19
  label: string;
20
20
  value: string | number;
21
21
  onCommit: (value: string) => void;
@@ -27,14 +27,21 @@ export declare function TextEdit({ label, value, onCommit, reason, multiline, pl
27
27
  min?: number;
28
28
  max?: number;
29
29
  required?: boolean;
30
- validate?: (value: string) => boolean;
30
+ isValid?: (value: string) => boolean;
31
31
  invalidMessage?: string;
32
32
  hideLabel?: boolean;
33
33
  disabled?: boolean;
34
34
  help?: string;
35
35
  }): import("react").JSX.Element;
36
36
  export declare const positiveInteger: (raw: string) => boolean;
37
- export declare function SelectEdit({ label, value, options, reason, onCommit, readOnlyValue, labelAction }: {
37
+ export type OptionGroup = {
38
+ label?: string;
39
+ options: readonly {
40
+ label: string;
41
+ value: string;
42
+ }[];
43
+ };
44
+ export declare function SelectEdit({ label, value, options, groups, reason, onCommit, readOnlyValue, labelAction }: {
38
45
  label: string;
39
46
  value: string;
40
47
  options: readonly {
@@ -45,8 +52,9 @@ export declare function SelectEdit({ label, value, options, reason, onCommit, re
45
52
  onCommit: (value: string) => void;
46
53
  readOnlyValue?: string;
47
54
  labelAction?: ReactNode;
55
+ groups?: readonly OptionGroup[];
48
56
  }): import("react").JSX.Element;
49
- export declare function CheckboxList({ label, value, options, reason, onToggle, readOnlyValue, searchable }: {
57
+ export declare function CheckboxList({ label, value, options, groups, reason, onToggle, readOnlyValue, searchable }: {
50
58
  label: string;
51
59
  value: string[];
52
60
  options: readonly {
@@ -57,6 +65,7 @@ export declare function CheckboxList({ label, value, options, reason, onToggle,
57
65
  onToggle: (value: string, checked: boolean) => void;
58
66
  readOnlyValue?: string;
59
67
  searchable?: boolean;
68
+ groups?: readonly OptionGroup[];
60
69
  }): import("react").JSX.Element;
61
70
  export declare function TestName({ test }: SectionProps): import("react").JSX.Element;
62
71
  export declare function Hypothesis({ test }: SectionProps): import("react").JSX.Element;