@delmaredigital/payload-experiments 0.1.0-rc.7 → 0.2.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +28 -0
  2. package/dist/admin/LayerUsage.d.ts +1 -1
  3. package/dist/admin/LayerUsage.js +23 -11
  4. package/dist/admin/MetricSourceSelect.js +2 -1
  5. package/dist/admin/TestsNavLink.d.ts +1 -0
  6. package/dist/admin/TestsNavLink.js +30 -0
  7. package/dist/admin/allocation.model.js +1 -1
  8. package/dist/admin/copy/actions.d.ts +0 -2
  9. package/dist/admin/copy/actions.js +0 -2
  10. package/dist/admin/copy/advanced.d.ts +55 -0
  11. package/dist/admin/copy/advanced.js +55 -0
  12. package/dist/admin/copy/armSelect.js +1 -1
  13. package/dist/admin/copy/arms.d.ts +0 -13
  14. package/dist/admin/copy/arms.js +0 -13
  15. package/dist/admin/copy/{who.d.ts → audience.d.ts} +1 -9
  16. package/dist/admin/copy/{who.js → audience.js} +1 -9
  17. package/dist/admin/copy/history.d.ts +0 -4
  18. package/dist/admin/copy/history.js +3 -7
  19. package/dist/admin/copy/metrics.d.ts +2 -2
  20. package/dist/admin/copy/metrics.js +2 -2
  21. package/dist/admin/copy/newTest.d.ts +32 -0
  22. package/dist/admin/copy/newTest.js +32 -0
  23. package/dist/admin/copy/readiness.d.ts +45 -0
  24. package/dist/admin/copy/readiness.js +50 -0
  25. package/dist/admin/copy/recommendation.d.ts +26 -0
  26. package/dist/admin/copy/recommendation.js +25 -0
  27. package/dist/admin/copy/results.d.ts +0 -1
  28. package/dist/admin/copy/results.js +0 -1
  29. package/dist/admin/copy/review.d.ts +0 -17
  30. package/dist/admin/copy/review.js +1 -112
  31. package/dist/admin/copy/setting.d.ts +10 -0
  32. package/dist/admin/copy/setting.js +10 -0
  33. package/dist/admin/copy/surface.d.ts +36 -0
  34. package/dist/admin/copy/surface.js +47 -0
  35. package/dist/admin/copy/testPage.d.ts +88 -0
  36. package/dist/admin/copy/testPage.js +88 -0
  37. package/dist/admin/paths.d.ts +5 -13
  38. package/dist/admin/paths.js +5 -13
  39. package/dist/admin/review/review.model.d.ts +3 -26
  40. package/dist/admin/review/review.model.js +3 -30
  41. package/dist/admin/surface/ResultsRoute.d.ts +7 -0
  42. package/dist/admin/surface/ResultsRoute.js +58 -0
  43. package/dist/admin/surface/SurfaceShell.d.ts +6 -0
  44. package/dist/admin/surface/SurfaceShell.js +35 -0
  45. package/dist/admin/surface/TestsHome.d.ts +4 -0
  46. package/dist/admin/surface/TestsHome.js +212 -0
  47. package/dist/admin/surface/TestsSurface.d.ts +2 -0
  48. package/dist/admin/surface/TestsSurface.js +36 -0
  49. package/dist/admin/surface/contracts.d.ts +48 -0
  50. package/dist/admin/surface/contracts.js +8 -0
  51. package/dist/admin/surface/home.model.d.ts +41 -0
  52. package/dist/admin/surface/home.model.js +59 -0
  53. package/dist/admin/surface/newTest/NewTest.d.ts +7 -0
  54. package/dist/admin/surface/newTest/NewTest.js +355 -0
  55. package/dist/admin/surface/newTest/newTest.css +9 -0
  56. package/dist/admin/surface/pageBlock/pageBlock.model.d.ts +6 -0
  57. package/dist/admin/surface/pageBlock/pageBlock.model.js +8 -0
  58. package/dist/admin/surface/pageBlock/parts.d.ts +2 -0
  59. package/dist/admin/surface/pageBlock/parts.js +64 -0
  60. package/dist/admin/surface/readiness/ActionDialogs.d.ts +15 -0
  61. package/dist/admin/surface/readiness/ActionDialogs.js +274 -0
  62. package/dist/admin/surface/readiness/Banner.d.ts +2 -0
  63. package/dist/admin/surface/readiness/Banner.js +138 -0
  64. package/dist/admin/surface/readiness/SaveStatus.d.ts +4 -0
  65. package/dist/admin/surface/readiness/SaveStatus.js +50 -0
  66. package/dist/admin/surface/readiness/parts.d.ts +2 -0
  67. package/dist/admin/surface/readiness/parts.js +9 -0
  68. package/dist/admin/surface/readiness/readiness.model.d.ts +22 -0
  69. package/dist/admin/surface/readiness/readiness.model.js +107 -0
  70. package/dist/admin/surface/readiness/useTransition.d.ts +10 -0
  71. package/dist/admin/surface/readiness/useTransition.js +34 -0
  72. package/dist/admin/surface/results/ResultsBody.d.ts +7 -0
  73. package/dist/admin/surface/results/ResultsBody.js +262 -0
  74. package/dist/admin/surface/results/recommendation.model.d.ts +12 -0
  75. package/dist/admin/surface/results/recommendation.model.js +59 -0
  76. package/dist/admin/surface/results/useResults.d.ts +56 -0
  77. package/dist/admin/surface/results/useResults.js +183 -0
  78. package/dist/admin/surface/setting/parts.d.ts +3 -0
  79. package/dist/admin/surface/setting/parts.js +263 -0
  80. package/dist/admin/surface/setting/setting.css +29 -0
  81. package/dist/admin/surface/setting/setting.model.d.ts +8 -0
  82. package/dist/admin/surface/setting/setting.model.js +25 -0
  83. package/dist/admin/surface/surface.css +50 -0
  84. package/dist/admin/surface/test/AdvancedSection.d.ts +2 -0
  85. package/dist/admin/surface/test/AdvancedSection.js +153 -0
  86. package/dist/admin/surface/test/GoalSection.d.ts +2 -0
  87. package/dist/admin/surface/test/GoalSection.js +198 -0
  88. package/dist/admin/surface/test/LengthSection.d.ts +2 -0
  89. package/dist/admin/surface/test/LengthSection.js +87 -0
  90. package/dist/admin/surface/test/Section.d.ts +7 -0
  91. package/dist/admin/surface/test/Section.js +27 -0
  92. package/dist/admin/surface/test/TargetingSection.d.ts +2 -0
  93. package/dist/admin/surface/test/TargetingSection.js +164 -0
  94. package/dist/admin/surface/test/TestHeader.d.ts +4 -0
  95. package/dist/admin/surface/test/TestHeader.js +69 -0
  96. package/dist/admin/surface/test/TestPage.d.ts +7 -0
  97. package/dist/admin/surface/test/TestPage.js +84 -0
  98. package/dist/admin/surface/test/VersionsSection.d.ts +4 -0
  99. package/dist/admin/surface/test/VersionsSection.js +208 -0
  100. package/dist/admin/surface/test/fields.d.ts +55 -0
  101. package/dist/admin/surface/test/fields.js +185 -0
  102. package/dist/admin/surface/test/pageParts.d.ts +2 -0
  103. package/dist/admin/surface/test/pageParts.js +39 -0
  104. package/dist/admin/surface/test/testPage.css +23 -0
  105. package/dist/admin/surface/test/testPage.model.d.ts +17 -0
  106. package/dist/admin/surface/test/testPage.model.js +53 -0
  107. package/dist/admin/surface/testDocument.model.d.ts +39 -0
  108. package/dist/admin/surface/testDocument.model.js +61 -0
  109. package/dist/admin/surface/useTestDocument.d.ts +22 -0
  110. package/dist/admin/surface/useTestDocument.js +345 -0
  111. package/dist/admin/targeting/targeting.model.js +2 -2
  112. package/dist/admin/useLayerUsage.d.ts +0 -5
  113. package/dist/admin/useLayerUsage.js +1 -39
  114. package/dist/admin/views/CollectionEditRedirect.d.ts +4 -0
  115. package/dist/admin/views/CollectionEditRedirect.js +14 -0
  116. package/dist/admin/views/CollectionListRedirect.d.ts +2 -0
  117. package/dist/admin/views/CollectionListRedirect.js +8 -0
  118. package/dist/admin/views/ExperimentsRootView.d.ts +2 -0
  119. package/dist/admin/views/ExperimentsRootView.js +175 -0
  120. package/dist/admin/views/access.model.d.ts +7 -0
  121. package/dist/admin/views/access.model.js +5 -0
  122. package/dist/collections/experiments/config.js +18 -125
  123. package/dist/collections/experiments/createDefaults.js +6 -0
  124. package/dist/collections/layers.js +43 -12
  125. package/dist/collections/metrics.js +70 -14
  126. package/dist/collections/serialize.js +4 -1
  127. package/dist/db/snapshots.d.ts +2 -1
  128. package/dist/db/snapshots.js +2 -2
  129. package/dist/endpoints/results.js +7 -0
  130. package/dist/endpoints/summary.d.ts +1 -1
  131. package/dist/endpoints/summary.js +37 -1
  132. package/dist/exports/adminClient.d.ts +2 -13
  133. package/dist/exports/adminClient.js +2 -13
  134. package/dist/exports/adminRsc.d.ts +3 -0
  135. package/dist/exports/adminRsc.js +3 -0
  136. package/dist/exports/index.d.ts +1 -1
  137. package/dist/options.js +29 -0
  138. package/dist/plugin.js +25 -0
  139. package/dist/puck/editor/CreateExperimentDrawer.d.ts +2 -1
  140. package/dist/puck/editor/CreateExperimentDrawer.js +17 -57
  141. package/dist/puck/editor/ExperimentSlotEditor.js +6 -0
  142. package/dist/puck/editor/copy.d.ts +10 -9
  143. package/dist/puck/editor/copy.js +11 -10
  144. package/dist/puck/editor/createTest.d.ts +7 -0
  145. package/dist/puck/editor/createTest.js +50 -0
  146. package/dist/puck/editor/editor.model.d.ts +28 -1
  147. package/dist/puck/editor/editor.model.js +61 -0
  148. package/dist/results/analyse.js +4 -1
  149. package/dist/results/verdict.d.ts +1 -0
  150. package/dist/results/verdict.js +7 -4
  151. package/dist/transitions/checkStart.d.ts +1 -0
  152. package/dist/transitions/placements.d.ts +5 -1
  153. package/dist/transitions/placements.js +14 -7
  154. package/dist/transitions/preflightData.d.ts +7 -1
  155. package/dist/transitions/preflightData.js +199 -148
  156. package/dist/transitions/start.js +2 -2
  157. package/dist/types/admin.d.ts +1 -0
  158. package/dist/types/contracts.d.ts +17 -0
  159. package/dist/types/endpoints.d.ts +11 -1
  160. package/dist/types/snapshot.d.ts +1 -0
  161. package/dist/types/surface.d.ts +29 -0
  162. package/dist/types/surface.js +1 -0
  163. package/package.json +7 -1
  164. package/dist/access.d.ts +0 -1
  165. package/dist/access.js +0 -2
  166. package/dist/admin/AllocationField.d.ts +0 -2
  167. package/dist/admin/AllocationField.js +0 -80
  168. package/dist/admin/FlagPointSelect.d.ts +0 -2
  169. package/dist/admin/FlagPointSelect.js +0 -54
  170. package/dist/admin/LockedFieldNotice.d.ts +0 -2
  171. package/dist/admin/LockedFieldNotice.js +0 -26
  172. package/dist/admin/PhaseHistory.d.ts +0 -2
  173. package/dist/admin/PhaseHistory.js +0 -76
  174. package/dist/admin/StatusCell.d.ts +0 -3
  175. package/dist/admin/StatusCell.js +0 -19
  176. package/dist/admin/SummaryCell.d.ts +0 -2
  177. package/dist/admin/SummaryCell.js +0 -32
  178. package/dist/admin/actions/AddMetricDialog.d.ts +0 -6
  179. package/dist/admin/actions/AddMetricDialog.js +0 -138
  180. package/dist/admin/actions/AllocateDialog.d.ts +0 -6
  181. package/dist/admin/actions/AllocateDialog.js +0 -81
  182. package/dist/admin/actions/ExperimentActions.d.ts +0 -3
  183. package/dist/admin/actions/ExperimentActions.js +0 -110
  184. package/dist/admin/arms/ArmsField.d.ts +0 -2
  185. package/dist/admin/arms/ArmsField.js +0 -310
  186. package/dist/admin/arms/ParamsEditor.d.ts +0 -16
  187. package/dist/admin/arms/ParamsEditor.js +0 -79
  188. package/dist/admin/copy/tabs.d.ts +0 -7
  189. package/dist/admin/copy/tabs.js +0 -7
  190. package/dist/admin/permissions.model.d.ts +0 -10
  191. package/dist/admin/permissions.model.js +0 -4
  192. package/dist/admin/phaseHistory.model.d.ts +0 -19
  193. package/dist/admin/phaseHistory.model.js +0 -19
  194. package/dist/admin/results/ResultsView.d.ts +0 -3
  195. package/dist/admin/results/ResultsView.js +0 -312
  196. package/dist/admin/review/ReviewPanel.d.ts +0 -2
  197. package/dist/admin/review/ReviewPanel.js +0 -268
  198. package/dist/admin/summaryBatcher.d.ts +0 -8
  199. package/dist/admin/summaryBatcher.js +0 -33
  200. package/dist/admin/targeting/TargetingAttributeField.d.ts +0 -2
  201. package/dist/admin/targeting/TargetingAttributeField.js +0 -31
  202. package/dist/admin/targeting/TargetingOpField.d.ts +0 -2
  203. package/dist/admin/targeting/TargetingOpField.js +0 -45
  204. package/dist/admin/targeting/TargetingValueField.d.ts +0 -2
  205. package/dist/admin/targeting/TargetingValueField.js +0 -142
  206. package/dist/index.d.ts +0 -1
  207. package/dist/index.js +0 -4
@@ -1,312 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import '../styles.js';
4
- import { useEffect, useMemo, useRef, useState } from 'react';
5
- import { Button, Form, useAuth, useDocumentInfo } from '@payloadcms/ui';
6
- import { useApi } from '../useApi.js';
7
- import { canManage } from '../permissions.model.js';
8
- import { armLabel } from '../labels.js';
9
- import { RESULTS_COPY as C } from '../copy/results.js';
10
- import { API_MESSAGES } from '../copy/common.js';
11
- import { ExperimentActions } from '../actions/ExperimentActions.js';
12
- import { PreviewLinks } from '../PreviewLinks.js';
13
- import { VerdictCard } from './VerdictCard.js';
14
- import { ArmTable } from './ArmTable.js';
15
- import { DataQualityPanel } from './DataQualityPanel.js';
16
- import { CiChart } from './CiChart.js';
17
- import { armRows, collectingLine, dataQuality, defaultRound, guardrailChips, powerLine, rolloutLine, roundOptions, verdictCard } from './results.model.js';
18
- function savedFormState(saved) {
19
- const fields = {};
20
- const field = (path, value)=>{
21
- fields[path] = {
22
- value,
23
- initialValue: value
24
- };
25
- };
26
- for (const path of [
27
- 'status',
28
- 'kind',
29
- 'allocation'
30
- ])field(path, saved?.[path]);
31
- saved?.arms?.forEach((arm, index)=>{
32
- for (const [key, value] of Object.entries(arm))field(`arms.${index}.${key}`, value);
33
- });
34
- for (const [key, value] of Object.entries(saved?.decision ?? {}))field(`decision.${key}`, value);
35
- for (const [key, value] of Object.entries(saved?.metrics ?? {}))field(`metrics.${key}`, value);
36
- return fields;
37
- }
38
- export function ResultsView(_props) {
39
- const { id, data, initialState } = useDocumentInfo();
40
- const { permissions } = useAuth();
41
- const formState = useMemo(()=>initialState ?? savedFormState(data), [
42
- initialState,
43
- data
44
- ]);
45
- if (id == null) return /*#__PURE__*/ _jsx("p", {
46
- children: C.empty
47
- });
48
- return /*#__PURE__*/ _jsx(ResultsContent, {
49
- id: id,
50
- saved: data,
51
- formState: formState,
52
- manage: canManage(permissions)
53
- }, id);
54
- }
55
- function ResultsContent({ id, saved, formState, manage }) {
56
- const api = useApi();
57
- const status = saved?.status ?? 'draft';
58
- const decision = saved?.decision;
59
- const [round, setRound] = useState(null);
60
- const [phases, setPhases] = useState([]);
61
- const [state, setState] = useState({
62
- round: null,
63
- kind: 'loading'
64
- });
65
- const [refresh, setRefresh] = useState(0);
66
- const [placements, setPlacements] = useState(null);
67
- const [previewError, setPreviewError] = useState(null);
68
- const [previewRefresh, setPreviewRefresh] = useState(0);
69
- const [busy, setBusy] = useState(false);
70
- const inFlight = useRef(false);
71
- const [actionError, setActionError] = useState(null);
72
- useEffect(()=>{
73
- let active = true;
74
- setState((previous)=>({
75
- round,
76
- kind: 'loading',
77
- data: previous.round === round ? previous.data : undefined
78
- }));
79
- void api.results(id, {
80
- ...round === null ? {} : {
81
- phase: round
82
- },
83
- series: true
84
- }).then((result)=>{
85
- if (!active) return;
86
- if (!result.ok || !result.data) {
87
- setState((previous)=>({
88
- round,
89
- kind: !result.ok && result.status === 403 ? 'forbidden' : 'error',
90
- data: previous.round === round ? previous.data : undefined,
91
- message: result.ok ? API_MESSAGES.unreadable : result.message
92
- }));
93
- return;
94
- }
95
- setPhases(result.data.phases);
96
- if (round === null) {
97
- const initial = defaultRound({
98
- phases: result.data.phases,
99
- decision,
100
- status
101
- });
102
- if (initial !== null) {
103
- setRound(initial);
104
- return;
105
- }
106
- }
107
- setState({
108
- round,
109
- kind: 'ready',
110
- data: result.data
111
- });
112
- });
113
- return ()=>{
114
- active = false;
115
- };
116
- }, [
117
- api,
118
- id,
119
- round,
120
- refresh,
121
- status,
122
- decision?.phase
123
- ]);
124
- useEffect(()=>{
125
- let active = true;
126
- setPlacements(null);
127
- setPreviewError(null);
128
- if (manage) void api.preflight(id).then((result)=>{
129
- if (!active) return;
130
- if (result.ok && result.data) setPlacements(result.data.placements);
131
- else setPreviewError(result.ok ? API_MESSAGES.unreadable : result.message);
132
- });
133
- return ()=>{
134
- active = false;
135
- };
136
- }, [
137
- api,
138
- id,
139
- manage,
140
- previewRefresh
141
- ]);
142
- async function recompute() {
143
- if (!manage || inFlight.current) return;
144
- inFlight.current = true;
145
- setBusy(true);
146
- setActionError(null);
147
- try {
148
- const result = await api.recompute(id);
149
- if (result.ok) setRefresh((value)=>value + 1);
150
- else setActionError(result.message);
151
- } finally{
152
- inFlight.current = false;
153
- setBusy(false);
154
- }
155
- }
156
- const response = state.round === round ? state.data : undefined;
157
- const phase = response?.phases.find((phase)=>phase.phase === round);
158
- const snapshot = response?.snapshot;
159
- const isDecidedTestRound = status === 'decided' && phase?.phaseKind === 'test' && decision?.phase === round;
160
- const winner = phase?.arms.find((arm)=>arm.key === decision?.winnerArm)?.label ?? armLabel(decision?.winnerArm ?? 'control');
161
- // Running is context for this round only, never the live status of an older round.
162
- const lineStatus = status === 'running' && round === defaultRound({
163
- phases,
164
- status
165
- }) ? 'running' : 'stopped';
166
- const context = {
167
- phase,
168
- inLayer: saved?.layer != null,
169
- layerSlices: saved?.layerSlices,
170
- allocation: saved?.allocation
171
- };
172
- const primary = phase?.metrics.find((metric)=>metric.role === 'primary');
173
- return /*#__PURE__*/ _jsxs("div", {
174
- className: "xp-results",
175
- style: {
176
- color: 'var(--theme-text)'
177
- },
178
- children: [
179
- phases.length > 0 ? /*#__PURE__*/ _jsxs("label", {
180
- children: [
181
- C.round,
182
- /*#__PURE__*/ _jsx("select", {
183
- value: round ?? '',
184
- onChange: (event)=>setRound(Number(event.target.value)),
185
- children: roundOptions(phases).map((option)=>/*#__PURE__*/ _jsx("option", {
186
- value: option.value,
187
- children: option.label
188
- }, option.value))
189
- })
190
- ]
191
- }) : null,
192
- state.round !== round || state.kind === 'loading' ? /*#__PURE__*/ _jsx("p", {
193
- role: "status",
194
- children: response ? C.refreshing : C.loading
195
- }) : state.kind === 'forbidden' ? /*#__PURE__*/ _jsx("p", {
196
- role: "alert",
197
- children: C.forbidden
198
- }) : state.kind === 'error' ? /*#__PURE__*/ _jsxs(_Fragment, {
199
- children: [
200
- /*#__PURE__*/ _jsx("p", {
201
- role: "alert",
202
- children: state.message
203
- }),
204
- /*#__PURE__*/ _jsx(Button, {
205
- onClick: ()=>setRefresh((value)=>value + 1),
206
- children: C.retry
207
- })
208
- ]
209
- }) : null,
210
- phase && snapshot ? /*#__PURE__*/ _jsxs(_Fragment, {
211
- children: [
212
- /*#__PURE__*/ _jsx(VerdictCard, {
213
- model: verdictCard({
214
- snapshot,
215
- decisionSnapshot: response?.decisionSnapshot,
216
- isDecidedTestRound,
217
- winnerLabel: winner
218
- }),
219
- guardrails: guardrailChips(snapshot),
220
- power: powerLine({
221
- snapshot,
222
- phaseKind: phase.phaseKind,
223
- status: lineStatus
224
- }),
225
- collecting: collectingLine({
226
- snapshot,
227
- phaseKind: phase.phaseKind,
228
- status: lineStatus
229
- }, context),
230
- sinceShipping: rolloutLine(snapshot, phase, phases, decision ?? undefined)
231
- }),
232
- /*#__PURE__*/ _jsx(DataQualityPanel, {
233
- items: dataQuality(snapshot, context)
234
- }),
235
- [
236
- 'primary',
237
- 'secondary',
238
- 'guardrail'
239
- ].map((role)=>{
240
- const metrics = phase.metrics.filter((metric)=>metric.role === role);
241
- return metrics.length ? /*#__PURE__*/ _jsxs("section", {
242
- children: [
243
- /*#__PURE__*/ _jsx("h2", {
244
- children: C[role]
245
- }),
246
- metrics.map((metric)=>/*#__PURE__*/ _jsx(ArmTable, {
247
- caption: metric.name,
248
- method: snapshot.method,
249
- metricKind: metric.kind,
250
- rows: armRows({
251
- snapshot,
252
- phase,
253
- metricId: metric.metricId
254
- })
255
- }, metric.metricId))
256
- ]
257
- }, role) : null;
258
- }),
259
- primary ? /*#__PURE__*/ _jsx(CiChart, {
260
- series: response?.series,
261
- metricId: primary.metricId,
262
- metricName: primary.name,
263
- phase: phase,
264
- contentChanges: snapshot.contentChanges
265
- }) : null
266
- ]
267
- }) : state.round === round && state.kind === 'ready' ? /*#__PURE__*/ _jsx("p", {
268
- children: C.empty
269
- }) : null,
270
- manage && placements ? /*#__PURE__*/ _jsx(PreviewLinks, {
271
- experimentId: id,
272
- placements: placements,
273
- arms: saved?.arms ?? []
274
- }) : null,
275
- manage && previewError ? /*#__PURE__*/ _jsxs(_Fragment, {
276
- children: [
277
- /*#__PURE__*/ _jsx("p", {
278
- role: "alert",
279
- children: previewError
280
- }),
281
- /*#__PURE__*/ _jsx(Button, {
282
- onClick: ()=>setPreviewRefresh((value)=>value + 1),
283
- children: C.retryPreview
284
- })
285
- ]
286
- }) : null,
287
- /*#__PURE__*/ _jsx(Form, {
288
- initialState: formState,
289
- el: "div",
290
- children: /*#__PURE__*/ _jsx(ExperimentActions, {
291
- field: {
292
- name: 'actions',
293
- type: 'ui',
294
- admin: {}
295
- },
296
- path: "actions"
297
- })
298
- }),
299
- manage ? /*#__PURE__*/ _jsx(Button, {
300
- disabled: busy,
301
- onClick: ()=>{
302
- void recompute();
303
- },
304
- children: busy ? C.recomputing : C.recompute
305
- }) : null,
306
- actionError ? /*#__PURE__*/ _jsx("p", {
307
- role: "alert",
308
- children: actionError
309
- }) : null
310
- ]
311
- });
312
- }
@@ -1,2 +0,0 @@
1
- import type { UIFieldClientProps } from 'payload';
2
- export declare function ReviewPanel(_props: UIFieldClientProps): import("react").JSX.Element;
@@ -1,268 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useEffect, useRef, useState } from 'react';
4
- import { Button, useConfig, useDocumentInfo, useForm, useFormFields, useFormModified } from '@payloadcms/ui';
5
- import { useApi } from '../useApi.js';
6
- import { reloadPage } from '../reload.js';
7
- import { readArms } from '../arms/arms.model.js';
8
- import { PreviewLinks } from '../PreviewLinks.js';
9
- import { REVIEW_COPY as C } from '../copy/review.js';
10
- import { API_MESSAGES, LOCKED_MESSAGES } from '../copy/common.js';
11
- import { availableActions } from '../actions/actions.model.js';
12
- import { ConfirmDialog } from '../actions/ConfirmDialog.js';
13
- import { reviewView } from './review.model.js';
14
- export function ReviewPanel(_props) {
15
- const { id, data } = useDocumentInfo();
16
- const savedAt = typeof data?.updatedAt === 'string' ? data.updatedAt : undefined;
17
- const { config, getEntityConfig } = useConfig();
18
- const api = useApi();
19
- const { dispatchFields, setModified } = useForm();
20
- const modified = useFormModified();
21
- const status = useFormFields(([fields])=>fields.status?.value ?? 'draft');
22
- const kind = useFormFields(([fields])=>fields.kind?.value ?? 'block');
23
- const armsJson = useFormFields(([fields])=>JSON.stringify(readArms(fields)));
24
- const arms = JSON.parse(armsJson);
25
- const metricFields = getEntityConfig({
26
- collectionSlug: 'xp-metrics'
27
- })?.fields;
28
- const sourcesJson = JSON.stringify(metricFields?.find((field)=>field.name === 'source')?.admin?.custom?.sources ?? []);
29
- const [preflight, setPreflight] = useState(null);
30
- const [refresh, setRefresh] = useState(0);
31
- const [loading, setLoading] = useState(true);
32
- const [busy, setBusy] = useState(false);
33
- const [confirmStart, setConfirmStart] = useState(false);
34
- const inFlight = useRef(false);
35
- const [error, setError] = useState(null);
36
- const [metricKind, setMetricKind] = useState();
37
- const [duration, setDuration] = useState('');
38
- const [tuning, setTuning] = useState('');
39
- useEffect(()=>{
40
- if (id == null) return;
41
- let active = true;
42
- setLoading(true);
43
- setError(null);
44
- void api.preflight(id).then((result)=>{
45
- if (!active) return;
46
- if (result.ok && result.data) {
47
- setPreflight(result.data);
48
- setDuration(result.data.estimate ? String(result.data.estimate.plannedDurationDays) : '');
49
- setTuning(result.data.estimate ? String(result.data.estimate.sequentialTuningParameter) : '');
50
- } else {
51
- setPreflight(null);
52
- setError(result.ok ? API_MESSAGES.unreadable : result.message);
53
- }
54
- setLoading(false);
55
- });
56
- return ()=>{
57
- active = false;
58
- };
59
- }, [
60
- id,
61
- api,
62
- refresh,
63
- savedAt
64
- ]);
65
- // The preflight wire shape contains the value, but the saved metric's source supplies its kind.
66
- const metricId = preflight?.baseline?.metricId;
67
- useEffect(()=>{
68
- setMetricKind(undefined);
69
- if (metricId == null) return;
70
- const controller = new AbortController();
71
- void (async ()=>{
72
- try {
73
- const response = await fetch(`${config.routes.api.replace(/\/$/, '')}/xp-metrics/${encodeURIComponent(metricId)}?depth=0`, {
74
- credentials: 'include',
75
- signal: controller.signal
76
- });
77
- if (!response.ok) return;
78
- const metric = await response.json();
79
- const sources = JSON.parse(sourcesJson);
80
- if (!controller.signal.aborted) setMetricKind(sources.find((source)=>source.id === metric.source)?.kind);
81
- } catch {}
82
- })();
83
- return ()=>controller.abort();
84
- }, [
85
- metricId,
86
- config.routes.api,
87
- sourcesJson
88
- ]);
89
- if (id == null) return /*#__PURE__*/ _jsx("p", {
90
- children: C.save
91
- });
92
- const view = preflight ? reviewView(preflight, {
93
- status,
94
- primaryMetricKind: metricKind
95
- }) : null;
96
- const editable = status === 'draft' || status === 'stopped';
97
- const startAction = availableActions({
98
- status,
99
- kind,
100
- arms,
101
- canManage: true
102
- }).find((action)=>action.id === 'start');
103
- const pending = busy || confirmStart;
104
- const lockedReason = status === 'running' ? LOCKED_MESSAGES.running : status === 'decided' ? LOCKED_MESSAGES.decided : LOCKED_MESSAGES.archived;
105
- const validValues = duration.trim() !== '' && Number.isInteger(Number(duration)) && Number(duration) > 0 && tuning.trim() !== '' && Number.isFinite(Number(tuning)) && Number(tuning) >= 0;
106
- function apply() {
107
- if (!editable || pending || loading || !view?.applyValues || !validValues) return;
108
- dispatchFields({
109
- type: 'UPDATE',
110
- path: 'analysis.plannedDurationDays',
111
- value: Number(duration)
112
- });
113
- dispatchFields({
114
- type: 'UPDATE',
115
- path: 'analysis.sequentialTuningParameter',
116
- value: Number(tuning)
117
- });
118
- setModified(true);
119
- setRefresh((value)=>value + 1);
120
- }
121
- async function start() {
122
- if (id == null || inFlight.current || pending || loading || modified || !view?.canStart) return;
123
- if (status === 'stopped') {
124
- setConfirmStart(true);
125
- return;
126
- }
127
- inFlight.current = true;
128
- setBusy(true);
129
- setError(null);
130
- const result = await api.start(id);
131
- if (result.ok) {
132
- reloadPage();
133
- return;
134
- }
135
- setError(result.message);
136
- inFlight.current = false;
137
- setBusy(false);
138
- }
139
- return /*#__PURE__*/ _jsxs("div", {
140
- className: "xp-review",
141
- children: [
142
- loading ? /*#__PURE__*/ _jsx("p", {
143
- role: "status",
144
- children: C.loading
145
- }) : null,
146
- view && preflight ? /*#__PURE__*/ _jsxs(_Fragment, {
147
- children: [
148
- /*#__PURE__*/ _jsx("ul", {
149
- children: view.checks.map((check, index)=>/*#__PURE__*/ _jsxs("li", {
150
- children: [
151
- /*#__PURE__*/ _jsxs("span", {
152
- children: [
153
- check.ok ? '✓' : '✗',
154
- " ",
155
- check.ok ? check.done : check.hint
156
- ]
157
- }),
158
- check.detail ? /*#__PURE__*/ _jsx("p", {
159
- children: check.detail
160
- }) : null
161
- ]
162
- }, index))
163
- }),
164
- /*#__PURE__*/ _jsx("ul", {
165
- children: view.placements.map((placement)=>/*#__PURE__*/ _jsxs("li", {
166
- children: [
167
- placement.ok ? '✓' : '✗',
168
- " ",
169
- placement.title,
170
- placement.detail ? /*#__PURE__*/ _jsx("p", {
171
- children: placement.detail
172
- }) : null
173
- ]
174
- }, `${placement.collection}/${placement.id}`))
175
- }),
176
- /*#__PURE__*/ _jsx("fieldset", {
177
- disabled: pending || loading,
178
- style: {
179
- border: 0,
180
- padding: 0
181
- },
182
- children: /*#__PURE__*/ _jsx(PreviewLinks, {
183
- experimentId: id,
184
- placements: preflight.placements,
185
- arms: arms
186
- })
187
- }),
188
- /*#__PURE__*/ _jsx("p", {
189
- children: view.baseline
190
- }),
191
- /*#__PURE__*/ _jsx("p", {
192
- children: view.traffic
193
- }),
194
- /*#__PURE__*/ _jsx("p", {
195
- className: view.estimateWarning ? 'xp-review__warning' : undefined,
196
- style: view.estimateWarning ? {
197
- color: 'var(--theme-warning-600)'
198
- } : undefined,
199
- children: view.estimate
200
- }),
201
- view.applyValues ? /*#__PURE__*/ _jsxs(_Fragment, {
202
- children: [
203
- /*#__PURE__*/ _jsxs("label", {
204
- children: [
205
- C.duration,
206
- /*#__PURE__*/ _jsx("input", {
207
- type: "number",
208
- min: 1,
209
- step: 1,
210
- value: duration,
211
- disabled: !editable || pending || loading,
212
- onChange: (event)=>setDuration(event.target.value)
213
- })
214
- ]
215
- }),
216
- /*#__PURE__*/ _jsxs("label", {
217
- children: [
218
- C.tuning,
219
- /*#__PURE__*/ _jsx("input", {
220
- type: "number",
221
- min: 0,
222
- step: "any",
223
- value: tuning,
224
- disabled: !editable || pending || loading,
225
- onChange: (event)=>setTuning(event.target.value)
226
- })
227
- ]
228
- }),
229
- /*#__PURE__*/ _jsx(Button, {
230
- disabled: !editable || pending || loading || !validValues,
231
- onClick: apply,
232
- children: C.apply
233
- })
234
- ]
235
- }) : null,
236
- !editable ? /*#__PURE__*/ _jsx("p", {
237
- children: lockedReason
238
- }) : null,
239
- modified ? /*#__PURE__*/ _jsx("p", {
240
- children: C.saveFirst
241
- }) : null,
242
- /*#__PURE__*/ _jsx(Button, {
243
- disabled: !view.canStart || modified || pending || loading,
244
- onClick: ()=>{
245
- void start();
246
- },
247
- children: startAction?.label ?? C.start
248
- })
249
- ]
250
- }) : null,
251
- error ? /*#__PURE__*/ _jsx("p", {
252
- role: "alert",
253
- children: error
254
- }) : null,
255
- error && !preflight ? /*#__PURE__*/ _jsx(Button, {
256
- disabled: pending || loading,
257
- onClick: ()=>setRefresh((value)=>value + 1),
258
- children: C.retry
259
- }) : null,
260
- confirmStart && startAction ? /*#__PURE__*/ _jsx(ConfirmDialog, {
261
- id: id,
262
- action: startAction,
263
- winnerLabel: "",
264
- onClose: ()=>setConfirmStart(false)
265
- }, `${id}/start`) : null
266
- ]
267
- });
268
- }
@@ -1,8 +0,0 @@
1
- import type { DocId } from '../types/contracts.js';
2
- import type { SummaryResponse } from '../types/endpoints.js';
3
- import type { createApi } from './api.js';
4
- export type SummaryEntry = SummaryResponse[DocId];
5
- type Api = Pick<ReturnType<typeof createApi>, 'summary'>;
6
- /** All list cells mounted in this tick share a request, even with separate useApi instances. */
7
- export declare function requestSummary(api: Api, id: DocId): Promise<SummaryEntry | null>;
8
- export {};
@@ -1,33 +0,0 @@
1
- let pending = [];
2
- /** All list cells mounted in this tick share a request, even with separate useApi instances. */ export function requestSummary(api, id) {
3
- return new Promise((resolve)=>{
4
- pending.push({
5
- id,
6
- resolve
7
- });
8
- if (pending.length !== 1) return;
9
- queueMicrotask(()=>{
10
- const batch = pending;
11
- pending = [];
12
- const ids = [
13
- ...new Map(batch.map((item)=>[
14
- String(item.id),
15
- item.id
16
- ])).values()
17
- ];
18
- for(let offset = 0; offset < ids.length; offset += 100){
19
- const chunk = ids.slice(offset, offset + 100);
20
- const keys = new Set(chunk.map(String));
21
- const callers = batch.filter((item)=>keys.has(String(item.id)));
22
- void (async ()=>{
23
- try {
24
- const result = await api.summary(chunk);
25
- for (const caller of callers)caller.resolve(result.ok ? result.data?.[caller.id] ?? null : null);
26
- } catch {
27
- for (const caller of callers)caller.resolve(null);
28
- }
29
- })();
30
- }
31
- });
32
- });
33
- }
@@ -1,2 +0,0 @@
1
- import type { TextFieldClientProps } from 'payload';
2
- export declare function TargetingAttributeField({ path, field, readOnly }: TextFieldClientProps): import("react").JSX.Element;
@@ -1,31 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { SelectInput, useField, useFormFields } from '@payloadcms/ui';
4
- import { isEditable } from '../../collections/experiments/editability.js';
5
- import { WHO_COPY } from '../copy/who.js';
6
- import { humaniseAttribute } from './targeting.model.js';
7
- export function TargetingAttributeField({ path, field, readOnly }) {
8
- const { value, setValue } = useField({
9
- path
10
- });
11
- const status = useFormFields(([fields])=>fields.status?.value ?? 'draft');
12
- const keys = field.admin?.custom?.attributeKeys ?? {};
13
- const locked = Boolean(readOnly || field.admin?.readOnly || !isEditable(status, 'targeting'));
14
- return /*#__PURE__*/ _jsx("div", {
15
- children: /*#__PURE__*/ _jsx(SelectInput, {
16
- path: path,
17
- name: path,
18
- label: WHO_COPY.attribute,
19
- required: field.required,
20
- options: Object.keys(keys).map((key)=>({
21
- label: humaniseAttribute(key),
22
- value: key
23
- })),
24
- value: value ?? '',
25
- readOnly: locked,
26
- onChange: (option)=>{
27
- if (!locked) setValue(option && !Array.isArray(option) ? option.value : null);
28
- }
29
- })
30
- });
31
- }
@@ -1,2 +0,0 @@
1
- import type { TextFieldClientProps } from 'payload';
2
- export declare function TargetingOpField({ path, field, readOnly }: TextFieldClientProps): import("react").JSX.Element;