@delmaredigital/payload-experiments 0.2.0-rc.7 → 0.3.0-rc.4

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 (336) hide show
  1. package/README.md +202 -26
  2. package/dist/admin/PreviewLinks.d.ts +2 -1
  3. package/dist/admin/PreviewLinks.js +50 -21
  4. package/dist/admin/TestsNavLink.d.ts +3 -1
  5. package/dist/admin/TestsNavLink.js +2 -2
  6. package/dist/admin/actions/ConfirmDialog.d.ts +7 -4
  7. package/dist/admin/actions/ConfirmDialog.js +37 -82
  8. package/dist/admin/actions/DecideDialog.d.ts +3 -1
  9. package/dist/admin/actions/DecideDialog.js +30 -13
  10. package/dist/admin/actions/MakeContentResult.d.ts +13 -2
  11. package/dist/admin/actions/MakeContentResult.js +115 -16
  12. package/dist/admin/actions/actions.model.d.ts +2 -4
  13. package/dist/admin/actions/actions.model.js +2 -3
  14. package/dist/admin/allocation.model.js +3 -5
  15. package/dist/admin/api.d.ts +10 -4
  16. package/dist/admin/api.js +11 -7
  17. package/dist/admin/armSelect/ArmSelectField.js +14 -22
  18. package/dist/admin/arms/arms.model.d.ts +1 -21
  19. package/dist/admin/arms/arms.model.js +1 -153
  20. package/dist/admin/copy/actions.d.ts +39 -3
  21. package/dist/admin/copy/actions.js +40 -4
  22. package/dist/admin/copy/armSelect.js +5 -5
  23. package/dist/admin/copy/arms.d.ts +1 -6
  24. package/dist/admin/copy/arms.js +1 -16
  25. package/dist/admin/copy/audience.d.ts +2 -1
  26. package/dist/admin/copy/audience.js +3 -2
  27. package/dist/admin/copy/common.d.ts +13 -6
  28. package/dist/admin/copy/common.js +14 -8
  29. package/dist/admin/copy/goals.d.ts +21 -6
  30. package/dist/admin/copy/goals.js +27 -6
  31. package/dist/admin/copy/readiness.d.ts +22 -0
  32. package/dist/admin/copy/readiness.js +21 -0
  33. package/dist/admin/copy/recommendation.d.ts +0 -3
  34. package/dist/admin/copy/recommendation.js +0 -3
  35. package/dist/admin/copy/results.d.ts +43 -2
  36. package/dist/admin/copy/results.js +44 -3
  37. package/dist/admin/copy/review.d.ts +2 -2
  38. package/dist/admin/copy/review.js +2 -2
  39. package/dist/admin/copy/setting.d.ts +2 -0
  40. package/dist/admin/copy/setting.js +2 -0
  41. package/dist/admin/copy/surface.d.ts +7 -5
  42. package/dist/admin/copy/surface.js +9 -5
  43. package/dist/admin/copy/testPage.d.ts +10 -0
  44. package/dist/admin/copy/testPage.js +10 -0
  45. package/dist/admin/format/date.d.ts +2 -0
  46. package/dist/admin/format/date.js +10 -0
  47. package/dist/admin/paths.d.ts +2 -1
  48. package/dist/admin/paths.js +2 -1
  49. package/dist/admin/previewLinks.model.d.ts +4 -2
  50. package/dist/admin/previewLinks.model.js +8 -7
  51. package/dist/admin/results/CiChart.js +11 -21
  52. package/dist/admin/results/DataQualityPanel.d.ts +4 -2
  53. package/dist/admin/results/DataQualityPanel.js +30 -17
  54. package/dist/admin/results/DataTable.d.ts +16 -0
  55. package/dist/admin/results/DataTable.js +152 -0
  56. package/dist/admin/results/StatTile.d.ts +7 -0
  57. package/dist/admin/results/StatTile.js +22 -0
  58. package/dist/admin/results/VerdictHero.d.ts +13 -0
  59. package/dist/admin/results/{VerdictCard.js → VerdictHero.js} +15 -12
  60. package/dist/admin/results/legacy.d.ts +16 -0
  61. package/dist/admin/results/legacy.js +27 -0
  62. package/dist/admin/results/releasePages.model.d.ts +17 -0
  63. package/dist/admin/results/releasePages.model.js +10 -0
  64. package/dist/admin/results/results.css +64 -0
  65. package/dist/admin/results/results.model.d.ts +11 -7
  66. package/dist/admin/results/results.model.js +24 -7
  67. package/dist/admin/results/snapshot.d.ts +14 -0
  68. package/dist/admin/results/snapshot.js +3 -0
  69. package/dist/admin/surface/ResultsRoute.d.ts +1 -1
  70. package/dist/admin/surface/ResultsRoute.js +59 -34
  71. package/dist/admin/surface/SurfaceShell.d.ts +11 -2
  72. package/dist/admin/surface/SurfaceShell.js +49 -14
  73. package/dist/admin/surface/TestsHome.d.ts +6 -1
  74. package/dist/admin/surface/TestsHome.js +182 -114
  75. package/dist/admin/surface/TestsSurface.js +56 -16
  76. package/dist/admin/surface/goals/GoalForm.js +341 -308
  77. package/dist/admin/surface/goals/GoalsList.js +123 -89
  78. package/dist/admin/surface/goals/data.d.ts +8 -2
  79. package/dist/admin/surface/goals/data.js +24 -16
  80. package/dist/admin/surface/goals/goals.css +10 -13
  81. package/dist/admin/surface/home.model.d.ts +8 -8
  82. package/dist/admin/surface/home.model.js +23 -12
  83. package/dist/admin/surface/newTest/NewTest.js +9 -9
  84. package/dist/admin/surface/pageBlock/parts.js +31 -26
  85. package/dist/admin/surface/readiness/ActionDialogs.js +47 -19
  86. package/dist/admin/surface/readiness/Banner.d.ts +3 -0
  87. package/dist/admin/surface/readiness/Banner.js +58 -33
  88. package/dist/admin/surface/readiness/SaveStatus.d.ts +1 -0
  89. package/dist/admin/surface/readiness/SaveStatus.js +6 -3
  90. package/dist/admin/surface/readiness/readiness.model.d.ts +2 -0
  91. package/dist/admin/surface/readiness/readiness.model.js +25 -9
  92. package/dist/admin/surface/readiness/useTransition.d.ts +1 -0
  93. package/dist/admin/surface/readiness/useTransition.js +2 -1
  94. package/dist/admin/surface/results/ReleasePages.d.ts +14 -0
  95. package/dist/admin/surface/results/ReleasePages.js +116 -0
  96. package/dist/admin/surface/results/ResultsBody.d.ts +11 -1
  97. package/dist/admin/surface/results/ResultsBody.js +277 -105
  98. package/dist/admin/surface/results/ResultsHeader.d.ts +10 -0
  99. package/dist/admin/surface/results/ResultsHeader.js +177 -0
  100. package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
  101. package/dist/admin/surface/results/recommendation.model.js +12 -9
  102. package/dist/admin/surface/results/useResults.d.ts +21 -3
  103. package/dist/admin/surface/results/useResults.js +2 -1
  104. package/dist/admin/surface/setting/parts.js +75 -88
  105. package/dist/admin/surface/setting/setting.css +3 -15
  106. package/dist/admin/surface/surface.css +61 -47
  107. package/dist/admin/surface/test/AdvancedSection.js +55 -48
  108. package/dist/admin/surface/test/GoalSection.js +46 -47
  109. package/dist/admin/surface/test/LengthSection.js +46 -34
  110. package/dist/admin/surface/test/Rail.d.ts +4 -0
  111. package/dist/admin/surface/test/Rail.js +83 -0
  112. package/dist/admin/surface/test/ReadinessBar.d.ts +5 -0
  113. package/dist/admin/surface/test/ReadinessBar.js +33 -0
  114. package/dist/admin/surface/test/Section.d.ts +1 -1
  115. package/dist/admin/surface/test/Section.js +36 -16
  116. package/dist/admin/surface/test/TargetingSection.js +168 -142
  117. package/dist/admin/surface/test/TestPage.d.ts +2 -1
  118. package/dist/admin/surface/test/TestPage.js +146 -68
  119. package/dist/admin/surface/test/VersionsSection.js +151 -120
  120. package/dist/admin/surface/test/fields.d.ts +11 -4
  121. package/dist/admin/surface/test/fields.js +84 -85
  122. package/dist/admin/surface/test/jumpToSection.d.ts +3 -0
  123. package/dist/admin/surface/test/jumpToSection.js +10 -0
  124. package/dist/admin/surface/test/pageParts.js +9 -3
  125. package/dist/admin/surface/test/testPage.css +81 -14
  126. package/dist/admin/surface/test/testPage.model.d.ts +2 -3
  127. package/dist/admin/surface/test/testPage.model.js +1 -21
  128. package/dist/admin/surface/useTestDocument.js +12 -5
  129. package/dist/admin/targeting/targeting.model.d.ts +1 -3
  130. package/dist/admin/targeting/targeting.model.js +3 -8
  131. package/dist/admin/theme.css +115 -36
  132. package/dist/admin/theme.d.ts +38 -0
  133. package/dist/admin/theme.js +38 -1
  134. package/dist/admin/useApi.d.ts +17 -2
  135. package/dist/admin/useLayerUsage.js +14 -23
  136. package/dist/admin/views/ExperimentsRootView.js +12 -2
  137. package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
  138. package/dist/admin/views/TestsNavLinkServer.js +43 -0
  139. package/dist/arms/copy.d.ts +6 -0
  140. package/dist/arms/copy.js +16 -0
  141. package/dist/arms/model.d.ts +23 -0
  142. package/dist/arms/model.js +173 -0
  143. package/dist/collections/experiments/config.js +25 -0
  144. package/dist/collections/experiments/lockHook.js +3 -1
  145. package/dist/collections/identities.js +6 -1
  146. package/dist/collections/metrics.js +7 -0
  147. package/dist/collections/metricsGuards.d.ts +9 -0
  148. package/dist/collections/metricsGuards.js +33 -0
  149. package/dist/contentDone.d.ts +4 -0
  150. package/dist/contentDone.js +5 -0
  151. package/dist/db/appends.d.ts +6 -2
  152. package/dist/db/appends.js +10 -4
  153. package/dist/db/identities.d.ts +8 -7
  154. package/dist/db/identities.js +9 -6
  155. package/dist/db/locks.d.ts +6 -0
  156. package/dist/db/locks.js +13 -1
  157. package/dist/db/metricUsage.d.ts +19 -0
  158. package/dist/db/metricUsage.js +54 -0
  159. package/dist/db/names.d.ts +5 -0
  160. package/dist/db/names.js +5 -0
  161. package/dist/db/pageOccupancy.d.ts +12 -0
  162. package/dist/db/pageOccupancy.js +45 -0
  163. package/dist/db/publishedContent.d.ts +10 -0
  164. package/dist/db/publishedContent.js +43 -0
  165. package/dist/db/results.d.ts +2 -0
  166. package/dist/db/results.js +6 -2
  167. package/dist/db/snapshots.d.ts +18 -6
  168. package/dist/db/snapshots.js +25 -8
  169. package/dist/db/sweep.js +11 -6
  170. package/dist/endpoints/index.js +19 -1
  171. package/dist/endpoints/results.js +5 -0
  172. package/dist/endpoints/sanitizeReadinessDetail.d.ts +1 -0
  173. package/dist/endpoints/sanitizeReadinessDetail.js +2 -0
  174. package/dist/endpoints/summary.js +20 -2
  175. package/dist/endpoints/transitions.d.ts +15 -0
  176. package/dist/endpoints/transitions.js +36 -0
  177. package/dist/errors.d.ts +8 -0
  178. package/dist/errors.js +28 -0
  179. package/dist/exports/adminRsc.d.ts +1 -0
  180. package/dist/exports/adminRsc.js +1 -0
  181. package/dist/exports/index.d.ts +2 -1
  182. package/dist/exports/index.js +1 -0
  183. package/dist/exports/puck.d.ts +2 -1
  184. package/dist/exports/puck.js +1 -1
  185. package/dist/exports/puckEditor.d.ts +4 -1
  186. package/dist/exports/puckEditor.js +3 -1
  187. package/dist/exports/rsc.d.ts +6 -1
  188. package/dist/exports/rsc.js +2 -0
  189. package/dist/hooks/pageGuard.d.ts +5 -0
  190. package/dist/hooks/pageGuard.js +139 -0
  191. package/dist/lib/fetchAllDocs.d.ts +16 -0
  192. package/dist/lib/fetchAllDocs.js +56 -0
  193. package/dist/log.js +6 -3
  194. package/dist/manifest/compile.d.ts +5 -0
  195. package/dist/manifest/compile.js +5 -1
  196. package/dist/options.js +16 -0
  197. package/dist/plugin.js +30 -2
  198. package/dist/puck/ExperimentPicker.js +47 -16
  199. package/dist/puck/ExperimentSlot.d.ts +51 -1
  200. package/dist/puck/ExperimentSlot.js +23 -12
  201. package/dist/puck/copy.d.ts +6 -5
  202. package/dist/puck/copy.js +6 -5
  203. package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
  204. package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
  205. package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
  206. package/dist/puck/editor/ResultsReadout.d.ts +6 -0
  207. package/dist/puck/editor/ResultsReadout.js +99 -0
  208. package/dist/puck/editor/VersionBuilder.d.ts +34 -0
  209. package/dist/puck/editor/VersionBuilder.js +317 -0
  210. package/dist/puck/editor/armWrites.d.ts +7 -0
  211. package/dist/puck/editor/armWrites.js +71 -0
  212. package/dist/puck/editor/armsCache.d.ts +14 -0
  213. package/dist/puck/editor/armsCache.js +78 -0
  214. package/dist/puck/editor/copy.d.ts +33 -1
  215. package/dist/puck/editor/copy.js +45 -8
  216. package/dist/puck/editor/createTest.d.ts +1 -1
  217. package/dist/puck/editor/createTest.js +25 -14
  218. package/dist/puck/editor/editor.chrome.css +121 -0
  219. package/dist/puck/editor/editor.css +288 -204
  220. package/dist/puck/editor/editor.model.d.ts +4 -1
  221. package/dist/puck/editor/editor.model.js +10 -7
  222. package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
  223. package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
  224. package/dist/puck/experimentSlotField.d.ts +2 -0
  225. package/dist/puck/experimentSlotField.js +11 -3
  226. package/dist/puck/hostContext.d.ts +10 -0
  227. package/dist/puck/hostContext.js +12 -0
  228. package/dist/puck/routes.d.ts +12 -0
  229. package/dist/puck/routes.js +11 -0
  230. package/dist/puck/slotConfig.d.ts +1 -1
  231. package/dist/puck/slotConfig.js +2 -2
  232. package/dist/results/aggregate.js +4 -3
  233. package/dist/results/analyse.d.ts +4 -1
  234. package/dist/results/analyse.js +27 -11
  235. package/dist/results/cuped.js +3 -2
  236. package/dist/results/identityCredit.d.ts +15 -7
  237. package/dist/results/identityCredit.js +32 -33
  238. package/dist/results/rolloutUnits.js +2 -1
  239. package/dist/results/snapshot.js +8 -2
  240. package/dist/results/srm.js +2 -1
  241. package/dist/sanitizeReadinessDetail.d.ts +7 -0
  242. package/dist/sanitizeReadinessDetail.js +63 -0
  243. package/dist/server/linkIdentity.d.ts +10 -1
  244. package/dist/server/linkIdentity.js +17 -15
  245. package/dist/server/metadata.d.ts +5 -0
  246. package/dist/server/metadata.js +18 -3
  247. package/dist/server/normaliseContentRelationships.d.ts +7 -0
  248. package/dist/server/normaliseContentRelationships.js +62 -0
  249. package/dist/server/recordContentChange.d.ts +1 -1
  250. package/dist/server/recordContentChange.js +17 -8
  251. package/dist/server/runResults.d.ts +3 -2
  252. package/dist/server/runResults.js +157 -35
  253. package/dist/targeting/values.d.ts +4 -0
  254. package/dist/targeting/values.js +10 -0
  255. package/dist/transitions/addMetric.js +8 -0
  256. package/dist/transitions/archive.js +2 -3
  257. package/dist/transitions/checkStart.d.ts +10 -3
  258. package/dist/transitions/checkStart.js +25 -0
  259. package/dist/transitions/contentFields.d.ts +7 -0
  260. package/dist/transitions/contentFields.js +20 -0
  261. package/dist/transitions/decide.js +22 -2
  262. package/dist/transitions/dismissContentEntry.d.ts +8 -0
  263. package/dist/transitions/dismissContentEntry.js +45 -0
  264. package/dist/transitions/makeContent.d.ts +5 -2
  265. package/dist/transitions/makeContent.js +217 -118
  266. package/dist/transitions/phaseRecord.d.ts +8 -2
  267. package/dist/transitions/placements.d.ts +3 -6
  268. package/dist/transitions/placements.js +12 -1
  269. package/dist/transitions/preflightData.d.ts +9 -1
  270. package/dist/transitions/preflightData.js +138 -20
  271. package/dist/transitions/releasePages.d.ts +8 -0
  272. package/dist/transitions/releasePages.js +26 -0
  273. package/dist/transitions/restoreContentEntry.d.ts +8 -0
  274. package/dist/transitions/restoreContentEntry.js +47 -0
  275. package/dist/transitions/start.js +62 -7
  276. package/dist/types/contracts.d.ts +24 -4
  277. package/dist/types/endpoints.d.ts +34 -3
  278. package/dist/types/index.d.ts +1 -0
  279. package/dist/types/index.js +1 -0
  280. package/dist/types/reasons.d.ts +3 -0
  281. package/dist/types/reasons.js +1 -0
  282. package/dist/types/snapshot.d.ts +6 -1
  283. package/dist/ui/Banner.d.ts +2 -0
  284. package/dist/ui/Banner.js +2 -0
  285. package/dist/ui/Breadcrumb.d.ts +7 -0
  286. package/dist/ui/Breadcrumb.js +25 -0
  287. package/dist/ui/Button.d.ts +15 -0
  288. package/dist/ui/Button.js +32 -0
  289. package/dist/ui/Card.d.ts +12 -0
  290. package/dist/ui/Card.js +40 -0
  291. package/dist/ui/Checkbox.d.ts +2 -0
  292. package/dist/ui/Checkbox.js +2 -0
  293. package/dist/{admin/components → ui}/ChipsInput.d.ts +4 -3
  294. package/dist/{admin/components → ui}/ChipsInput.js +20 -12
  295. package/dist/ui/Dialog.d.ts +13 -0
  296. package/dist/ui/Dialog.js +85 -0
  297. package/dist/ui/EmptyState.d.ts +2 -0
  298. package/dist/ui/EmptyState.js +2 -0
  299. package/dist/ui/Feedback.d.ts +24 -0
  300. package/dist/ui/Feedback.js +40 -0
  301. package/dist/ui/Inputs.d.ts +34 -0
  302. package/dist/ui/Inputs.js +225 -0
  303. package/dist/ui/NumberInput.d.ts +2 -0
  304. package/dist/ui/NumberInput.js +2 -0
  305. package/dist/ui/Range.d.ts +2 -0
  306. package/dist/ui/Range.js +2 -0
  307. package/dist/ui/SearchInput.d.ts +2 -0
  308. package/dist/ui/SearchInput.js +2 -0
  309. package/dist/ui/SectionCard.d.ts +2 -0
  310. package/dist/ui/SectionCard.js +2 -0
  311. package/dist/ui/Select.d.ts +2 -0
  312. package/dist/ui/Select.js +2 -0
  313. package/dist/ui/StatusChip.d.ts +2 -0
  314. package/dist/ui/StatusChip.js +2 -0
  315. package/dist/ui/Tabs.d.ts +16 -0
  316. package/dist/ui/Tabs.js +61 -0
  317. package/dist/ui/TextInput.d.ts +2 -0
  318. package/dist/ui/TextInput.js +2 -0
  319. package/dist/ui/Textarea.d.ts +2 -0
  320. package/dist/ui/Textarea.js +2 -0
  321. package/dist/ui/Toggle.d.ts +2 -0
  322. package/dist/ui/Toggle.js +2 -0
  323. package/dist/ui/accent.d.ts +40 -0
  324. package/dist/ui/accent.js +139 -0
  325. package/dist/ui/index.d.ts +9 -0
  326. package/dist/ui/index.js +9 -0
  327. package/dist/ui/ui.css +92 -0
  328. package/package.json +5 -2
  329. package/dist/admin/components/chipsInput.css +0 -11
  330. package/dist/admin/results/ArmTable.d.ts +0 -8
  331. package/dist/admin/results/ArmTable.js +0 -113
  332. package/dist/admin/results/VerdictCard.d.ts +0 -9
  333. package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +0 -5
  334. package/dist/admin/surface/goals/GoalsBreadcrumb.js +0 -47
  335. package/dist/admin/surface/test/TestHeader.d.ts +0 -4
  336. package/dist/admin/surface/test/TestHeader.js +0 -70
@@ -1,16 +1,32 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import { useRouter } from 'next/navigation.js';
5
- import { Link } from '@payloadcms/ui';
6
5
  import { formatAdminURL } from 'payload/shared';
7
- import { GOALS_COPY as C, goalUsedBy } from '../../copy/goals.js';
6
+ import { GOALS_COPY as C, goalDeleteLocked, goalInUse } from '../../copy/goals.js';
8
7
  import { metricSourceHint, metricSourceOptions } from '../../metricSource.model.js';
9
8
  import { validateCappingType, validateCappingValue } from '../../../collections/metricValidation.js';
10
9
  import { goalSlug, goalUsage } from './goals.model.js';
11
- import { goalPages, goalResponse } from './data.js';
12
- import { GoalsBreadcrumb } from './GoalsBreadcrumb.js';
10
+ import { GoalResponseError, goalPages, goalResponse } from './data.js';
11
+ import { SURFACE_COPY } from '../../copy/surface.js';
12
+ import { SurfaceShell } from '../SurfaceShell.js';
13
+ import { Button } from '../../../ui/Button.js';
14
+ import { TextInput, NumberInput, Select } from '../../../ui/Inputs.js';
15
+ import { Card } from '../../../ui/Card.js';
16
+ import { Banner } from '../../../ui/Feedback.js';
17
+ import { Dialog } from '../../../ui/Dialog.js';
13
18
  import './goals.css';
19
+ const fieldNames = {
20
+ name: 'name',
21
+ slug: 'slug',
22
+ source: 'source',
23
+ direction: 'direction',
24
+ attributionWindowHours: 'attributionWindowHours',
25
+ minSampleSize: 'minSampleSize',
26
+ 'capping.type': 'capType',
27
+ 'capping.value': 'capValue',
28
+ guardrailThreshold: 'warningPercent'
29
+ };
14
30
  const empty = ()=>({
15
31
  name: '',
16
32
  slug: '',
@@ -36,6 +52,13 @@ export function GoalForm({ surface, id, returnToTest }) {
36
52
  const [slugEdited, setSlugEdited] = useState(false);
37
53
  const [attempt, setAttempt] = useState(0);
38
54
  const inFlight = useRef(false);
55
+ const advanced = useRef(null);
56
+ const [confirmDelete, setConfirmDelete] = useState(false);
57
+ useEffect(()=>{
58
+ if (advanced.current && (fieldErrors.capType || fieldErrors.capValue || fieldErrors.warningPercent)) advanced.current.open = true;
59
+ }, [
60
+ fieldErrors
61
+ ]);
39
62
  const returnQuery = returnToTest ? `?${new URLSearchParams({
40
63
  return: returnToTest
41
64
  })}` : '';
@@ -54,7 +77,9 @@ export function GoalForm({ surface, id, returnToTest }) {
54
77
  fetch(`${base}/xp-metrics/${encodeURIComponent(id)}?depth=0`, {
55
78
  credentials: 'include',
56
79
  signal: controller.signal
57
- }).then((response)=>goalResponse(response, C.loadError)),
80
+ }).then((response)=>goalResponse(response, C.loadError, {
81
+ notFound: C.notFound
82
+ })),
58
83
  goalPages(base, 'xp-experiments', controller.signal)
59
84
  ]);
60
85
  if (loaded.id == null) throw new Error(C.missing);
@@ -90,7 +115,7 @@ export function GoalForm({ surface, id, returnToTest }) {
90
115
  [key]: value
91
116
  }));
92
117
  async function submit() {
93
- if (inFlight.current || !ready) return;
118
+ if (inFlight.current || !ready || !surface.canManage) return;
94
119
  const capValue = draft.capValue.trim() === '' ? null : Number(draft.capValue);
95
120
  const typeError = validateCappingType(draft.capType || null, capValue, source?.kind);
96
121
  const valueError = validateCappingValue(capValue, draft.capType || null);
@@ -116,7 +141,7 @@ export function GoalForm({ surface, id, returnToTest }) {
116
141
  const body = {
117
142
  name: draft.name,
118
143
  slug: usedBy && goal ? goal.slug : draft.slug,
119
- source: draft.source,
144
+ source: usedBy && goal ? goal.source : draft.source,
120
145
  direction: draft.direction,
121
146
  attributionWindowHours: draft.attributionWindowHours,
122
147
  minSampleSize: draft.minSampleSize,
@@ -134,7 +159,9 @@ export function GoalForm({ surface, id, returnToTest }) {
134
159
  },
135
160
  body: JSON.stringify(body)
136
161
  });
137
- const saved = await goalResponse(response, C.saveError);
162
+ const saved = await goalResponse(response, C.saveError, {
163
+ notFound: C.notFound
164
+ });
138
165
  if (saved.doc?.id == null) throw new Error(C.saveError);
139
166
  router.push(id === undefined && returnToTest ? formatAdminURL({
140
167
  adminRoute: surface.adminRoute,
@@ -144,21 +171,30 @@ export function GoalForm({ surface, id, returnToTest }) {
144
171
  }) : listUrl);
145
172
  } catch (cause) {
146
173
  setError(cause instanceof Error ? cause.message : C.saveError);
174
+ if (cause instanceof GoalResponseError) setFieldErrors(Object.fromEntries(Object.entries(cause.fields).flatMap(([path, message])=>fieldNames[path] ? [
175
+ [
176
+ fieldNames[path],
177
+ message
178
+ ]
179
+ ] : [])));
147
180
  inFlight.current = false;
148
181
  setPending(null);
149
182
  }
150
183
  }
151
184
  async function remove() {
152
- if (inFlight.current || !ready || usedBy || id === undefined) return;
185
+ if (inFlight.current || !confirmDelete || !ready || !surface.canManage || usedBy || id === undefined) return;
153
186
  inFlight.current = true;
154
187
  setPending('delete');
155
188
  setError('');
189
+ setConfirmDelete(false);
156
190
  try {
157
191
  const response = await fetch(`${base}/xp-metrics/${encodeURIComponent(id)}`, {
158
192
  method: 'DELETE',
159
193
  credentials: 'include'
160
194
  });
161
- await goalResponse(response, C.deleteError);
195
+ await goalResponse(response, C.deleteError, {
196
+ notFound: C.notFound
197
+ });
162
198
  router.push(listUrl);
163
199
  } catch (cause) {
164
200
  setError(cause instanceof Error ? cause.message : C.deleteError);
@@ -166,305 +202,302 @@ export function GoalForm({ surface, id, returnToTest }) {
166
202
  setPending(null);
167
203
  }
168
204
  }
169
- return /*#__PURE__*/ _jsxs("div", {
170
- className: "xp-goals",
171
- children: [
172
- /*#__PURE__*/ _jsx(GoalsBreadcrumb, {
173
- adminRoute: surface.adminRoute,
174
- name: goal?.name ?? (id === undefined ? C.newGoal : undefined),
175
- returnQuery: returnQuery
176
- }),
177
- /*#__PURE__*/ _jsx("h1", {
178
- children: goal?.name ?? (id === undefined ? C.newGoal : C.title)
179
- }),
180
- loadError ? /*#__PURE__*/ _jsxs("p", {
181
- role: "alert",
182
- children: [
183
- loadError,
184
- ' ',
185
- /*#__PURE__*/ _jsx("button", {
186
- type: "button",
205
+ const title = goal?.name ?? (id === undefined ? C.newGoal : C.goal);
206
+ return /*#__PURE__*/ _jsx(SurfaceShell, {
207
+ adminRoute: surface.adminRoute,
208
+ title: title,
209
+ crumbs: [
210
+ {
211
+ label: SURFACE_COPY.title,
212
+ href: formatAdminURL({
213
+ adminRoute: surface.adminRoute,
214
+ path: '/experiments'
215
+ })
216
+ },
217
+ {
218
+ label: C.title,
219
+ href: listUrl
220
+ },
221
+ {
222
+ label: title
223
+ }
224
+ ],
225
+ status: usedBy ? /*#__PURE__*/ _jsx("span", {
226
+ className: "xp-tests-kind",
227
+ children: goalInUse(usedBy)
228
+ }) : undefined,
229
+ children: /*#__PURE__*/ _jsxs("div", {
230
+ className: "xp-goals",
231
+ children: [
232
+ loadError ? /*#__PURE__*/ _jsx(Banner, {
233
+ tone: "error",
234
+ action: /*#__PURE__*/ _jsx(Button, {
235
+ size: "sm",
187
236
  onClick: ()=>setAttempt((n)=>n + 1),
188
237
  children: C.retry
189
- })
190
- ]
191
- }) : !ready ? /*#__PURE__*/ _jsx("p", {
192
- role: "status",
193
- children: C.loading
194
- }) : /*#__PURE__*/ _jsxs("form", {
195
- className: "xp-goal-form",
196
- onSubmit: (event)=>{
197
- event.preventDefault();
198
- void submit();
199
- },
200
- children: [
201
- /*#__PURE__*/ _jsxs("fieldset", {
202
- disabled: pending !== null,
203
- children: [
204
- /*#__PURE__*/ _jsxs("label", {
205
- className: "xp-field",
206
- children: [
207
- C.name,
208
- /*#__PURE__*/ _jsx("input", {
209
- required: true,
210
- value: draft.name,
211
- onChange: (event)=>{
212
- const name = event.currentTarget.value;
213
- setDraft((current)=>({
214
- ...current,
215
- name,
216
- ...!slugEdited && !usedBy ? {
217
- slug: goalSlug(name)
218
- } : {}
219
- }));
220
- }
221
- })
222
- ]
223
- }),
224
- /*#__PURE__*/ _jsxs("label", {
225
- className: "xp-field",
226
- children: [
227
- C.slug,
228
- /*#__PURE__*/ _jsx("input", {
229
- required: true,
230
- value: draft.slug,
231
- readOnly: usedBy > 0,
232
- "aria-describedby": "xp-goal-slug-help",
233
- onChange: (event)=>{
234
- setSlugEdited(true);
235
- set('slug', event.currentTarget.value);
236
- }
237
- })
238
- ]
239
- }),
240
- /*#__PURE__*/ _jsx("p", {
241
- id: "xp-goal-slug-help",
242
- children: usedBy ? C.slugLocked : C.slugHelp
243
- }),
244
- /*#__PURE__*/ _jsxs("label", {
245
- className: "xp-field",
246
- children: [
247
- C.source,
248
- /*#__PURE__*/ _jsxs("select", {
249
- required: true,
250
- value: draft.source,
251
- onChange: (event)=>{
252
- const sourceId = event.currentTarget.value;
253
- const revenue = surface.metricSources.find((source)=>source.id === sourceId)?.kind === 'revenue';
254
- setDraft((current)=>({
255
- ...current,
256
- source: sourceId,
257
- capType: revenue ? 'percentile' : '',
258
- capValue: revenue ? '0.99' : ''
259
- }));
260
- },
261
- children: [
262
- /*#__PURE__*/ _jsx("option", {
263
- value: "",
264
- children: C.chooseSource
265
- }),
266
- options.map((option)=>/*#__PURE__*/ _jsx("option", {
267
- value: option.value,
268
- children: option.label
269
- }, option.value)),
270
- draft.source && !options.some((option)=>option.value === draft.source) ? /*#__PURE__*/ _jsx("option", {
271
- value: draft.source,
272
- children: draft.source
273
- }) : null
274
- ]
275
- })
276
- ]
277
- }),
278
- hint ? /*#__PURE__*/ _jsx("p", {
279
- children: hint
280
- }) : null,
281
- /*#__PURE__*/ _jsxs("label", {
282
- className: "xp-field",
283
- children: [
284
- C.direction,
285
- /*#__PURE__*/ _jsxs("select", {
286
- value: draft.direction,
287
- onChange: (event)=>set('direction', event.currentTarget.value),
288
- children: [
289
- /*#__PURE__*/ _jsx("option", {
290
- value: "increase",
291
- children: C.increase
292
- }),
293
- /*#__PURE__*/ _jsx("option", {
294
- value: "decrease",
295
- children: C.decrease
296
- })
297
- ]
298
- })
299
- ]
300
- }),
301
- /*#__PURE__*/ _jsxs("label", {
302
- className: "xp-field",
303
- children: [
304
- C.window,
305
- /*#__PURE__*/ _jsx("input", {
306
- required: true,
307
- type: "number",
308
- min: "1",
309
- step: "any",
310
- value: draft.attributionWindowHours ?? '',
311
- onChange: (event)=>set('attributionWindowHours', event.currentTarget.value === '' ? null : Number(event.currentTarget.value))
312
- })
313
- ]
314
- }),
315
- /*#__PURE__*/ _jsxs("label", {
316
- className: "xp-field",
317
- children: [
318
- C.minimum,
319
- /*#__PURE__*/ _jsx("input", {
320
- required: true,
321
- type: "number",
322
- min: "1",
323
- step: "any",
324
- value: draft.minSampleSize ?? '',
325
- onChange: (event)=>set('minSampleSize', event.currentTarget.value === '' ? null : Number(event.currentTarget.value))
326
- })
327
- ]
328
- }),
329
- /*#__PURE__*/ _jsxs("details", {
330
- children: [
331
- /*#__PURE__*/ _jsx("summary", {
332
- children: C.advanced
333
- }),
334
- /*#__PURE__*/ _jsxs("fieldset", {
335
- children: [
336
- /*#__PURE__*/ _jsx("legend", {
337
- children: C.capping
338
- }),
339
- /*#__PURE__*/ _jsxs("label", {
340
- className: "xp-field",
341
- children: [
342
- C.capType,
343
- /*#__PURE__*/ _jsxs("select", {
344
- required: source?.kind === 'revenue',
345
- value: draft.capType,
346
- "aria-invalid": fieldErrors.capType ? true : undefined,
347
- "aria-describedby": fieldErrors.capType ? 'xp-goal-cap-type-error' : undefined,
348
- onChange: (event)=>{
349
- const capType = event.currentTarget.value;
350
- setDraft((current)=>({
351
- ...current,
352
- capType,
353
- capValue: capType ? current.capValue : ''
354
- }));
355
- },
356
- children: [
357
- source?.kind !== 'revenue' ? /*#__PURE__*/ _jsx("option", {
358
- value: "",
359
- children: C.noCap
360
- }) : null,
361
- /*#__PURE__*/ _jsx("option", {
362
- value: "percentile",
363
- children: C.percentile
364
- }),
365
- /*#__PURE__*/ _jsx("option", {
366
- value: "absolute",
367
- children: C.absolute
368
- })
369
- ]
370
- })
371
- ]
372
- }),
373
- fieldErrors.capType ? /*#__PURE__*/ _jsx("p", {
374
- id: "xp-goal-cap-type-error",
375
- role: "alert",
376
- children: fieldErrors.capType
377
- }) : null,
378
- /*#__PURE__*/ _jsxs("label", {
379
- className: "xp-field",
380
- children: [
381
- C.capValue,
382
- /*#__PURE__*/ _jsx("input", {
383
- type: "number",
384
- step: "any",
385
- min: "0",
386
- disabled: !draft.capType,
387
- value: draft.capValue,
388
- "aria-invalid": fieldErrors.capValue ? true : undefined,
389
- "aria-describedby": fieldErrors.capValue ? 'xp-goal-cap-value-error' : undefined,
390
- onChange: (event)=>set('capValue', event.currentTarget.value)
391
- })
392
- ]
393
- }),
394
- /*#__PURE__*/ _jsx("p", {
395
- children: C.capHelp
396
- }),
397
- fieldErrors.capValue ? /*#__PURE__*/ _jsx("p", {
398
- id: "xp-goal-cap-value-error",
399
- role: "alert",
400
- children: fieldErrors.capValue
401
- }) : null
402
- ]
403
- }),
404
- /*#__PURE__*/ _jsxs("label", {
405
- className: "xp-field",
406
- children: [
407
- C.threshold,
408
- /*#__PURE__*/ _jsx("input", {
409
- type: "number",
410
- min: "0",
411
- step: "any",
412
- value: draft.warningPercent,
413
- "aria-invalid": fieldErrors.warningPercent ? true : undefined,
414
- "aria-describedby": fieldErrors.warningPercent ? 'xp-goal-threshold-error' : undefined,
415
- onInvalid: ()=>setFieldErrors((current)=>({
416
- ...current,
417
- warningPercent: C.thresholdError
418
- })),
419
- onChange: (event)=>set('warningPercent', event.currentTarget.value)
420
- })
421
- ]
422
- }),
423
- fieldErrors.warningPercent ? /*#__PURE__*/ _jsx("p", {
424
- id: "xp-goal-threshold-error",
425
- role: "alert",
426
- children: fieldErrors.warningPercent
427
- }) : null
428
- ]
429
- })
430
- ]
431
238
  }),
432
- error ? /*#__PURE__*/ _jsx("p", {
433
- role: "alert",
434
- children: error
435
- }) : null,
436
- /*#__PURE__*/ _jsxs("div", {
437
- className: "xp-tests-toolbar",
438
- children: [
439
- /*#__PURE__*/ _jsx("button", {
440
- type: "submit",
441
- disabled: pending !== null,
442
- children: pending === 'save' ? C.saving : C.save
443
- }),
444
- /*#__PURE__*/ _jsx(Link, {
445
- className: "xp-goal-cancel",
446
- href: listUrl,
447
- children: C.cancel
448
- }),
449
- id === undefined ? null : /*#__PURE__*/ _jsxs(_Fragment, {
450
- children: [
451
- /*#__PURE__*/ _jsx("button", {
452
- type: "button",
453
- disabled: pending !== null || usedBy > 0,
454
- "aria-describedby": usedBy ? 'xp-goal-used' : undefined,
455
- onClick: ()=>void remove(),
456
- children: pending === 'delete' ? C.deleting : C.remove
457
- }),
458
- usedBy ? /*#__PURE__*/ _jsx("span", {
459
- id: "xp-goal-used",
460
- children: goalUsedBy(usedBy)
461
- }) : null
462
- ]
463
- })
464
- ]
465
- })
466
- ]
467
- })
468
- ]
239
+ children: loadError
240
+ }) : !ready ? /*#__PURE__*/ _jsx("p", {
241
+ role: "status",
242
+ children: C.loading
243
+ }) : /*#__PURE__*/ _jsxs("form", {
244
+ className: "xp-goal-form",
245
+ onSubmit: (event)=>{
246
+ event.preventDefault();
247
+ void submit();
248
+ },
249
+ children: [
250
+ error ? /*#__PURE__*/ _jsx(Banner, {
251
+ tone: "error",
252
+ children: error
253
+ }) : null,
254
+ /*#__PURE__*/ _jsxs(Card, {
255
+ className: "xp-goal-card",
256
+ children: [
257
+ /*#__PURE__*/ _jsx("h2", {
258
+ children: C.fields
259
+ }),
260
+ /*#__PURE__*/ _jsxs("fieldset", {
261
+ disabled: pending !== null || !surface.canManage,
262
+ children: [
263
+ /*#__PURE__*/ _jsx(TextInput, {
264
+ id: "xp-goal-name",
265
+ label: C.name,
266
+ required: true,
267
+ value: draft.name,
268
+ error: fieldErrors.name,
269
+ onChange: (event)=>{
270
+ const name = event.currentTarget.value;
271
+ setDraft((current)=>({
272
+ ...current,
273
+ name,
274
+ ...!slugEdited && !usedBy ? {
275
+ slug: goalSlug(name)
276
+ } : {}
277
+ }));
278
+ }
279
+ }),
280
+ /*#__PURE__*/ _jsx(TextInput, {
281
+ id: "xp-goal-slug",
282
+ label: C.slug,
283
+ required: true,
284
+ value: draft.slug,
285
+ readOnly: usedBy > 0,
286
+ help: usedBy ? C.slugLocked : C.slugHelp,
287
+ error: fieldErrors.slug,
288
+ onChange: (event)=>{
289
+ setSlugEdited(true);
290
+ set('slug', event.currentTarget.value);
291
+ }
292
+ }),
293
+ /*#__PURE__*/ _jsxs(Select, {
294
+ id: "xp-goal-source",
295
+ label: C.source,
296
+ required: true,
297
+ value: draft.source,
298
+ disabled: usedBy > 0,
299
+ help: usedBy ? C.sourceLocked : hint || undefined,
300
+ error: fieldErrors.source,
301
+ onChange: (event)=>{
302
+ const sourceId = event.currentTarget.value;
303
+ const revenue = surface.metricSources.find((source)=>source.id === sourceId)?.kind === 'revenue';
304
+ setDraft((current)=>({
305
+ ...current,
306
+ source: sourceId,
307
+ capType: revenue ? 'percentile' : '',
308
+ capValue: revenue ? '0.99' : ''
309
+ }));
310
+ },
311
+ children: [
312
+ /*#__PURE__*/ _jsx("option", {
313
+ value: "",
314
+ children: C.chooseSource
315
+ }),
316
+ options.map((option)=>/*#__PURE__*/ _jsx("option", {
317
+ value: option.value,
318
+ children: option.label
319
+ }, option.value)),
320
+ draft.source && !options.some((option)=>option.value === draft.source) ? /*#__PURE__*/ _jsx("option", {
321
+ value: draft.source,
322
+ children: draft.source
323
+ }) : null
324
+ ]
325
+ }),
326
+ /*#__PURE__*/ _jsxs(Select, {
327
+ id: "xp-goal-direction",
328
+ label: C.direction,
329
+ value: draft.direction,
330
+ error: fieldErrors.direction,
331
+ onChange: (event)=>set('direction', event.currentTarget.value),
332
+ children: [
333
+ /*#__PURE__*/ _jsx("option", {
334
+ value: "increase",
335
+ children: C.increase
336
+ }),
337
+ /*#__PURE__*/ _jsx("option", {
338
+ value: "decrease",
339
+ children: C.decrease
340
+ })
341
+ ]
342
+ }),
343
+ /*#__PURE__*/ _jsx(NumberInput, {
344
+ id: "xp-goal-window",
345
+ label: C.window,
346
+ required: true,
347
+ min: "1",
348
+ step: "any",
349
+ value: draft.attributionWindowHours ?? '',
350
+ error: fieldErrors.attributionWindowHours,
351
+ onChange: (event)=>set('attributionWindowHours', event.currentTarget.value === '' ? null : Number(event.currentTarget.value))
352
+ }),
353
+ /*#__PURE__*/ _jsx(NumberInput, {
354
+ id: "xp-goal-minimum",
355
+ label: C.minimum,
356
+ required: true,
357
+ min: "1",
358
+ step: "any",
359
+ value: draft.minSampleSize ?? '',
360
+ error: fieldErrors.minSampleSize,
361
+ onChange: (event)=>set('minSampleSize', event.currentTarget.value === '' ? null : Number(event.currentTarget.value))
362
+ }),
363
+ /*#__PURE__*/ _jsxs("details", {
364
+ className: "xp-goal-advanced",
365
+ ref: advanced,
366
+ children: [
367
+ /*#__PURE__*/ _jsx("summary", {
368
+ children: C.advanced
369
+ }),
370
+ /*#__PURE__*/ _jsxs("div", {
371
+ className: "xp-goal-advanced-fields",
372
+ children: [
373
+ /*#__PURE__*/ _jsxs("fieldset", {
374
+ children: [
375
+ /*#__PURE__*/ _jsx("legend", {
376
+ children: C.capping
377
+ }),
378
+ /*#__PURE__*/ _jsxs(Select, {
379
+ id: "xp-goal-cap-type",
380
+ label: C.capType,
381
+ required: source?.kind === 'revenue',
382
+ value: draft.capType,
383
+ error: fieldErrors.capType,
384
+ onChange: (event)=>{
385
+ const capType = event.currentTarget.value;
386
+ setDraft((current)=>({
387
+ ...current,
388
+ capType,
389
+ capValue: capType ? current.capValue : ''
390
+ }));
391
+ },
392
+ children: [
393
+ source?.kind !== 'revenue' ? /*#__PURE__*/ _jsx("option", {
394
+ value: "",
395
+ children: C.noCap
396
+ }) : null,
397
+ /*#__PURE__*/ _jsx("option", {
398
+ value: "percentile",
399
+ children: C.percentile
400
+ }),
401
+ /*#__PURE__*/ _jsx("option", {
402
+ value: "absolute",
403
+ children: C.absolute
404
+ })
405
+ ]
406
+ }),
407
+ /*#__PURE__*/ _jsx(NumberInput, {
408
+ id: "xp-goal-cap-value",
409
+ label: C.capValue,
410
+ step: "any",
411
+ min: "0",
412
+ disabled: !draft.capType,
413
+ value: draft.capValue,
414
+ error: fieldErrors.capValue,
415
+ help: C.capHelp,
416
+ onChange: (event)=>set('capValue', event.currentTarget.value)
417
+ })
418
+ ]
419
+ }),
420
+ /*#__PURE__*/ _jsx(NumberInput, {
421
+ id: "xp-goal-threshold",
422
+ label: C.threshold,
423
+ min: "0",
424
+ step: "any",
425
+ value: draft.warningPercent,
426
+ error: fieldErrors.warningPercent,
427
+ onInvalid: ()=>setFieldErrors((current)=>({
428
+ ...current,
429
+ warningPercent: C.thresholdError
430
+ })),
431
+ onChange: (event)=>set('warningPercent', event.currentTarget.value)
432
+ })
433
+ ]
434
+ })
435
+ ]
436
+ })
437
+ ]
438
+ }),
439
+ /*#__PURE__*/ _jsxs("div", {
440
+ className: "xp-ui-button-group",
441
+ children: [
442
+ /*#__PURE__*/ _jsx(Button, {
443
+ variant: "primary",
444
+ type: "submit",
445
+ disabled: pending !== null || !surface.canManage,
446
+ busy: pending === 'save',
447
+ children: pending === 'save' ? C.saving : C.save
448
+ }),
449
+ /*#__PURE__*/ _jsx(Button, {
450
+ variant: "quiet",
451
+ href: listUrl,
452
+ children: C.cancel
453
+ }),
454
+ id === undefined ? null : /*#__PURE__*/ _jsx(Button, {
455
+ variant: "danger",
456
+ disabled: pending !== null || usedBy > 0 || !surface.canManage,
457
+ "aria-describedby": usedBy ? 'xp-goal-used' : undefined,
458
+ onClick: ()=>setConfirmDelete(true),
459
+ children: pending === 'delete' ? C.deleting : C.remove
460
+ })
461
+ ]
462
+ }),
463
+ id !== undefined && usedBy ? /*#__PURE__*/ _jsx("p", {
464
+ className: "xp-ui-help",
465
+ id: "xp-goal-used",
466
+ children: goalDeleteLocked(usedBy)
467
+ }) : null
468
+ ]
469
+ })
470
+ ]
471
+ }),
472
+ /*#__PURE__*/ _jsxs(Dialog, {
473
+ open: confirmDelete,
474
+ initialFocus: "cancel",
475
+ title: C.deleteTitle,
476
+ onClose: ()=>setConfirmDelete(false),
477
+ children: [
478
+ /*#__PURE__*/ _jsx("p", {
479
+ children: C.deleteBody
480
+ }),
481
+ /*#__PURE__*/ _jsxs("div", {
482
+ className: "xp-ui-button-group",
483
+ children: [
484
+ /*#__PURE__*/ _jsx(Button, {
485
+ variant: "quiet",
486
+ "data-dialog-cancel": true,
487
+ onClick: ()=>setConfirmDelete(false),
488
+ children: C.cancel
489
+ }),
490
+ /*#__PURE__*/ _jsx(Button, {
491
+ variant: "danger",
492
+ disabled: pending !== null,
493
+ onClick: ()=>void remove(),
494
+ children: C.confirmDelete
495
+ })
496
+ ]
497
+ })
498
+ ]
499
+ })
500
+ ]
501
+ })
469
502
  });
470
503
  }