@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.
- package/README.md +113 -20
- package/dist/admin/MetricSourceSelect.js +3 -3
- package/dist/admin/PlacementStatus.d.ts +3 -0
- package/dist/admin/PlacementStatus.js +54 -0
- package/dist/admin/PreviewLinks.d.ts +2 -1
- package/dist/admin/PreviewLinks.js +2 -2
- package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
- package/dist/admin/actions/ConfirmDialog.js +2 -2
- package/dist/admin/actions/DecideDialog.d.ts +2 -1
- package/dist/admin/actions/DecideDialog.js +2 -2
- package/dist/admin/api.d.ts +11 -2
- package/dist/admin/api.js +20 -2
- package/dist/admin/blocks/BlockPanel.d.ts +4 -0
- package/dist/admin/blocks/BlockPanel.js +153 -0
- package/dist/admin/blocks/TestBlockField.d.ts +5 -0
- package/dist/admin/blocks/TestBlockField.js +163 -0
- package/dist/admin/blocks/formApi.d.ts +43 -0
- package/dist/admin/blocks/formApi.js +153 -0
- package/dist/admin/blocks/inject.d.ts +7 -0
- package/dist/admin/blocks/inject.js +41 -0
- package/dist/admin/blocks/model.d.ts +17 -0
- package/dist/admin/blocks/model.js +45 -0
- package/dist/admin/blocks/statusCache.d.ts +7 -0
- package/dist/admin/blocks/statusCache.js +83 -0
- package/dist/admin/blocks/useBlockForm.d.ts +17 -0
- package/dist/admin/blocks/useBlockForm.js +46 -0
- package/dist/admin/copy/actions.d.ts +2 -0
- package/dist/admin/copy/actions.js +9 -0
- package/dist/admin/copy/blocks.d.ts +27 -0
- package/dist/admin/copy/blocks.js +38 -0
- package/dist/admin/copy/goals.d.ts +2 -0
- package/dist/admin/copy/goals.js +4 -1
- package/dist/admin/copy/newTest.d.ts +10 -0
- package/dist/admin/copy/newTest.js +10 -0
- package/dist/admin/copy/pages.d.ts +26 -0
- package/dist/admin/copy/pages.js +26 -0
- package/dist/admin/copy/readiness.d.ts +22 -3
- package/dist/admin/copy/readiness.js +23 -3
- package/dist/admin/copy/results.d.ts +1 -0
- package/dist/admin/copy/results.js +1 -0
- package/dist/admin/copy/testPage.d.ts +4 -0
- package/dist/admin/copy/testPage.js +4 -0
- package/dist/admin/goals/XpGoalField.d.ts +2 -0
- package/dist/admin/goals/XpGoalField.js +152 -0
- package/dist/admin/metricSource.model.d.ts +2 -0
- package/dist/admin/metricSource.model.js +4 -0
- package/dist/admin/pageCollections.d.ts +10 -0
- package/dist/admin/pageCollections.js +20 -0
- package/dist/admin/paths.d.ts +6 -0
- package/dist/admin/paths.js +6 -0
- package/dist/admin/placementStatus.model.d.ts +2 -0
- package/dist/admin/placementStatus.model.js +19 -0
- package/dist/admin/placements/placementLabels.d.ts +11 -0
- package/dist/admin/placements/placementLabels.js +21 -0
- package/dist/admin/placements/placementLabels.model.d.ts +13 -0
- package/dist/admin/placements/placementLabels.model.js +12 -0
- package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
- package/dist/admin/placements/usePlacementLabels.js +102 -0
- package/dist/admin/results/DataQualityPanel.js +4 -1
- package/dist/admin/results/results.model.d.ts +3 -0
- package/dist/admin/results/results.model.js +8 -4
- package/dist/admin/surface/TestsHome.js +37 -6
- package/dist/admin/surface/TestsSurface.js +5 -1
- package/dist/admin/surface/contracts.d.ts +6 -5
- package/dist/admin/surface/goals/GoalForm.js +67 -34
- package/dist/admin/surface/goals/GoalsList.js +3 -3
- package/dist/admin/surface/goals/data.js +4 -1
- package/dist/admin/surface/goals/goals.model.d.ts +1 -2
- package/dist/admin/surface/goals/goals.model.js +1 -3
- package/dist/admin/surface/home.model.d.ts +1 -1
- package/dist/admin/surface/home.model.js +8 -4
- package/dist/admin/surface/newTest/NewTest.js +54 -13
- package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
- package/dist/admin/surface/newTest/PageWizard.js +209 -0
- package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
- package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
- package/dist/admin/surface/pagePicker/PagePicker.d.ts +10 -0
- package/dist/admin/surface/pagePicker/PagePicker.js +172 -0
- package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +25 -0
- package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
- package/dist/admin/surface/readiness/ActionDialogs.js +99 -64
- package/dist/admin/surface/readiness/Banner.js +30 -6
- package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
- package/dist/admin/surface/readiness/readiness.model.js +91 -9
- package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
- package/dist/admin/surface/results/ReleasePages.js +3 -7
- package/dist/admin/surface/results/ResultsBody.js +21 -2
- package/dist/admin/surface/results/ResultsHeader.js +2 -5
- package/dist/admin/surface/results/useResults.d.ts +10 -1
- package/dist/admin/surface/test/AdvancedSection.js +111 -104
- package/dist/admin/surface/test/GoalSection.js +90 -7
- package/dist/admin/surface/test/LengthSection.js +1 -1
- package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
- package/dist/admin/surface/test/PageHoldout.js +67 -0
- package/dist/admin/surface/test/TestPage.js +2 -1
- package/dist/admin/surface/test/VersionsSection.js +12 -5
- package/dist/admin/surface/test/fields.d.ts +13 -4
- package/dist/admin/surface/test/fields.js +46 -14
- package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
- package/dist/admin/surface/test/jumpToSection.js +4 -3
- package/dist/admin/surface/test/pageParts.d.ts +4 -1
- package/dist/admin/surface/test/pageParts.js +79 -2
- package/dist/admin/surface/test/testPage.css +8 -1
- package/dist/admin/surface/testDocument.model.d.ts +1 -0
- package/dist/admin/surface/testDocument.model.js +2 -1
- package/dist/admin/surface/useTestDocument.d.ts +11 -2
- package/dist/admin/surface/useTestDocument.js +53 -15
- package/dist/admin/useApi.d.ts +11 -2
- package/dist/admin/useApi.js +3 -5
- package/dist/admin/views/ExperimentsRootView.js +58 -16
- package/dist/blocks/bindings.d.ts +3 -0
- package/dist/blocks/bindings.js +34 -0
- package/dist/blocks/createExperimentBlock.d.ts +6 -0
- package/dist/blocks/createExperimentBlock.js +77 -0
- package/dist/blocks/formState.d.ts +31 -0
- package/dist/blocks/formState.js +146 -0
- package/dist/blocks/model.d.ts +41 -0
- package/dist/blocks/model.js +34 -0
- package/dist/client/ExperimentGroup.d.ts +6 -0
- package/dist/client/ExperimentGroup.js +9 -0
- package/dist/client/ExperimentsProvider.js +20 -4
- package/dist/client/clickGoals.d.ts +4 -0
- package/dist/client/clickGoals.js +43 -0
- package/dist/collections/experiments/config.js +12 -2
- package/dist/collections/experiments/deleteGuard.d.ts +2 -1
- package/dist/collections/experiments/deleteGuard.js +12 -1
- package/dist/collections/experiments/editability.js +4 -2
- package/dist/collections/experiments/pickSlug.d.ts +2 -0
- package/dist/collections/experiments/pickSlug.js +8 -0
- package/dist/collections/labels.d.ts +3 -0
- package/dist/collections/labels.js +20 -0
- package/dist/collections/metrics.js +12 -2
- package/dist/collections/phases.js +1 -0
- package/dist/collections/serialize.js +7 -0
- package/dist/db/clickGoals.d.ts +9 -0
- package/dist/db/clickGoals.js +22 -0
- package/dist/db/delete.d.ts +1 -0
- package/dist/db/delete.js +1 -0
- package/dist/db/locks.d.ts +8 -0
- package/dist/db/locks.js +27 -4
- package/dist/db/names.d.ts +4 -0
- package/dist/db/names.js +4 -0
- package/dist/db/pageBindings.d.ts +4 -0
- package/dist/db/pageBindings.js +17 -0
- package/dist/db/pageBusy.d.ts +2 -0
- package/dist/db/pageBusy.js +10 -0
- package/dist/db/pageContent.d.ts +10 -0
- package/dist/db/pageContent.js +79 -0
- package/dist/db/placements.d.ts +33 -0
- package/dist/db/placements.js +54 -0
- package/dist/db/slugs.d.ts +8 -0
- package/dist/db/slugs.js +40 -0
- package/dist/db/snapshots.d.ts +6 -0
- package/dist/db/snapshots.js +8 -5
- package/dist/endpoints/clickKeys.d.ts +5 -0
- package/dist/endpoints/clickKeys.js +17 -0
- package/dist/endpoints/events.js +4 -1
- package/dist/endpoints/index.js +28 -0
- package/dist/endpoints/pages.d.ts +5 -0
- package/dist/endpoints/pages.js +120 -0
- package/dist/endpoints/placementStatus.d.ts +6 -0
- package/dist/endpoints/placementStatus.js +238 -0
- package/dist/endpoints/readBody.d.ts +2 -0
- package/dist/endpoints/readBody.js +24 -0
- package/dist/endpoints/results.js +5 -1
- package/dist/endpoints/summary.js +11 -4
- package/dist/endpoints/wrapBlock.d.ts +6 -0
- package/dist/endpoints/wrapBlock.js +76 -0
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +4 -4
- package/dist/events/rows.d.ts +1 -1
- package/dist/events/rows.js +5 -3
- package/dist/exports/adminClient.d.ts +5 -0
- package/dist/exports/adminClient.js +5 -0
- package/dist/exports/blocks.d.ts +2 -0
- package/dist/exports/blocks.js +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +1 -0
- package/dist/exports/index.d.ts +6 -1
- package/dist/exports/index.js +3 -0
- package/dist/exports/puck.d.ts +3 -1
- package/dist/exports/puck.js +2 -0
- package/dist/exports/puckEditor.d.ts +3 -0
- package/dist/exports/puckEditor.js +2 -0
- package/dist/exports/rsc.d.ts +3 -0
- package/dist/exports/rsc.js +2 -0
- package/dist/fields/xpGoal.d.ts +3 -0
- package/dist/fields/xpGoal.js +28 -0
- package/dist/globals/state.js +10 -0
- package/dist/goals/cache.d.ts +3 -0
- package/dist/goals/cache.js +35 -0
- package/dist/goals/fairness.d.ts +40 -0
- package/dist/goals/fairness.js +200 -0
- package/dist/goals/key.d.ts +4 -0
- package/dist/goals/key.js +3 -0
- package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
- package/dist/goals/picker/clickGoalPicker.model.js +45 -0
- package/dist/goals/picker/copy.d.ts +16 -0
- package/dist/goals/picker/copy.js +15 -0
- package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
- package/dist/goals/picker/useClickGoalPicker.js +220 -0
- package/dist/hooks/pageGuard.d.ts +5 -1
- package/dist/hooks/pageGuard.js +157 -95
- package/dist/onInit.js +29 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.js +41 -1
- package/dist/plugin.js +41 -28
- package/dist/puck/copy.d.ts +1 -0
- package/dist/puck/copy.js +1 -0
- package/dist/puck/editor/editor.chrome.css +1 -0
- package/dist/puck/editor/editor.css +1 -0
- package/dist/puck/editor/editor.model.d.ts +4 -2
- package/dist/puck/editor/editor.model.js +6 -3
- package/dist/puck/editor/integration.d.ts +10 -0
- package/dist/puck/editor/integration.js +30 -0
- package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
- package/dist/puck/editor/xpGoalPuckEditorField.js +134 -0
- package/dist/puck/integration.d.ts +6 -0
- package/dist/puck/integration.js +21 -0
- package/dist/puck/xpGoalField.d.ts +3 -0
- package/dist/puck/xpGoalField.js +6 -0
- package/dist/results/health.d.ts +4 -0
- package/dist/results/health.js +27 -0
- package/dist/results/snapshot.js +15 -2
- package/dist/sanitizeReadinessDetail.js +41 -0
- package/dist/server/pageKindSeoContract.d.ts +27 -0
- package/dist/server/pageKindSeoContract.js +80 -0
- package/dist/server/recordContentChange.js +58 -8
- package/dist/server/resolveExperimentBlocks.d.ts +5 -0
- package/dist/server/resolveExperimentBlocks.js +54 -0
- package/dist/server/runResults.js +56 -15
- package/dist/server/toClientAssignments.js +2 -1
- package/dist/transitions/addMetric.d.ts +2 -1
- package/dist/transitions/addMetric.js +28 -1
- package/dist/transitions/checkStart.d.ts +13 -1
- package/dist/transitions/checkStart.js +89 -7
- package/dist/transitions/decide.js +24 -0
- package/dist/transitions/deleteExperiment.d.ts +3 -1
- package/dist/transitions/deleteExperiment.js +27 -1
- package/dist/transitions/kernel.js +4 -0
- package/dist/transitions/makeContent.d.ts +5 -0
- package/dist/transitions/makeContent.js +39 -11
- package/dist/transitions/pageCollection.d.ts +10 -0
- package/dist/transitions/pageCollection.js +20 -0
- package/dist/transitions/pageVersions.d.ts +10 -0
- package/dist/transitions/pageVersions.js +44 -0
- package/dist/transitions/phaseRecord.d.ts +1 -0
- package/dist/transitions/placementInteractions.d.ts +40 -0
- package/dist/transitions/placementInteractions.js +94 -0
- package/dist/transitions/placements.d.ts +8 -0
- package/dist/transitions/placements.js +148 -1
- package/dist/transitions/preflightData.d.ts +2 -2
- package/dist/transitions/preflightData.js +137 -5
- package/dist/transitions/resolveCaps.d.ts +1 -0
- package/dist/transitions/resolveCaps.js +6 -2
- package/dist/transitions/start.js +45 -1
- package/dist/types/admin.d.ts +1 -1
- package/dist/types/assignments.d.ts +1 -0
- package/dist/types/contracts.d.ts +15 -0
- package/dist/types/endpoints.d.ts +92 -5
- package/dist/types/index.d.ts +3 -0
- package/dist/types/puck.d.ts +7 -1
- package/dist/types/snapshot.d.ts +17 -4
- package/dist/types/surface.d.ts +13 -0
- package/package.json +8 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useId, useState } from 'react';
|
|
4
|
-
import { TextInput, NumberInput, Textarea, Select, SearchInput, CheckboxList as UICheckboxList } from '../../../ui/Inputs.js';
|
|
4
|
+
import { TextInput, NumberInput, Textarea, Select, SearchInput, Checkbox, CheckboxList as UICheckboxList } from '../../../ui/Inputs.js';
|
|
5
5
|
import { TEST_PAGE as C } from '../../copy/testPage.js';
|
|
6
6
|
import { EDITABLE_BY_STATUS } from '../../../collections/experiments/editability.js';
|
|
7
7
|
import { lockedReason } from '../../lockedNotice.model.js';
|
|
@@ -45,7 +45,7 @@ export function ReadOnly({ label, value, reason, labelAction }) {
|
|
|
45
45
|
]
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
/** Local typing is intentionally separate from staging: build the current group on commit. */ export function TextEdit({ label, value, onCommit, reason, multiline, placeholder, type = 'text', min, max, required, onDraft,
|
|
48
|
+
/** Local typing is intentionally separate from staging: build the current group on commit. */ export function TextEdit({ label, value, onCommit, reason, multiline, placeholder, type = 'text', min, max, required, onDraft, isValid, invalidMessage, hideLabel = false, disabled = false, help }) {
|
|
49
49
|
const id = useId();
|
|
50
50
|
const [draft, setDraft] = useState(String(value));
|
|
51
51
|
const [error, setError] = useState(null);
|
|
@@ -54,7 +54,8 @@ export function ReadOnly({ label, value, reason, labelAction }) {
|
|
|
54
54
|
]);
|
|
55
55
|
const commit = ()=>{
|
|
56
56
|
if (draft === String(value)) return;
|
|
57
|
-
|
|
57
|
+
// Named isValid, not validate: Payload 4's Local API has a validate method, which the access gate flags.
|
|
58
|
+
if (isValid && !isValid(draft)) {
|
|
58
59
|
setError(draft.trim() ? invalidMessage ?? null : null);
|
|
59
60
|
setDraft(String(value));
|
|
60
61
|
onDraft?.(String(value));
|
|
@@ -96,7 +97,7 @@ export function ReadOnly({ label, value, reason, labelAction }) {
|
|
|
96
97
|
});
|
|
97
98
|
}
|
|
98
99
|
export const positiveInteger = (raw)=>raw.trim() !== '' && Number.isInteger(Number(raw)) && Number(raw) > 0;
|
|
99
|
-
export function SelectEdit({ label, value, options, reason, onCommit, readOnlyValue, labelAction }) {
|
|
100
|
+
export function SelectEdit({ label, value, options, groups, reason, onCommit, readOnlyValue, labelAction }) {
|
|
100
101
|
const id = useId();
|
|
101
102
|
if (reason) {
|
|
102
103
|
return /*#__PURE__*/ _jsx(ReadOnly, {
|
|
@@ -112,13 +113,22 @@ export function SelectEdit({ label, value, options, reason, onCommit, readOnlyVa
|
|
|
112
113
|
labelAction: labelAction,
|
|
113
114
|
value: value,
|
|
114
115
|
onChange: (e)=>onCommit(e.target.value),
|
|
115
|
-
children:
|
|
116
|
+
children: groups ? groups.map((group, index)=>group.label ? /*#__PURE__*/ _jsx("optgroup", {
|
|
117
|
+
label: group.label,
|
|
118
|
+
children: group.options.map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
119
|
+
value: option.value,
|
|
120
|
+
children: option.label
|
|
121
|
+
}, option.value))
|
|
122
|
+
}, index) : group.options.map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
123
|
+
value: option.value,
|
|
124
|
+
children: option.label
|
|
125
|
+
}, option.value))) : options.map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
116
126
|
value: option.value,
|
|
117
127
|
children: option.label
|
|
118
128
|
}, option.value))
|
|
119
129
|
});
|
|
120
130
|
}
|
|
121
|
-
export function CheckboxList({ label, value, options, reason, onToggle, readOnlyValue, searchable = false }) {
|
|
131
|
+
export function CheckboxList({ label, value, options, groups, reason, onToggle, readOnlyValue, searchable = false }) {
|
|
122
132
|
const id = useId();
|
|
123
133
|
const [query, setQuery] = useState('');
|
|
124
134
|
const visible = searchable ? options.filter((option)=>value.includes(option.value) || option.label.toLowerCase().includes(query.toLowerCase())) : options;
|
|
@@ -127,21 +137,43 @@ export function CheckboxList({ label, value, options, reason, onToggle, readOnly
|
|
|
127
137
|
reason: reason,
|
|
128
138
|
value: readOnlyValue ?? value.map((v)=>options.find((o)=>o.value === v)?.label ?? v).join(', ')
|
|
129
139
|
});
|
|
130
|
-
return /*#__PURE__*/
|
|
140
|
+
return /*#__PURE__*/ _jsxs(UICheckboxList, {
|
|
131
141
|
id: id,
|
|
132
142
|
label: label,
|
|
133
143
|
value: value,
|
|
134
|
-
options: visible,
|
|
144
|
+
options: groups ? [] : visible,
|
|
135
145
|
onChange: (next)=>{
|
|
136
146
|
const changed = options.find((option)=>next.includes(option.value) !== value.includes(option.value));
|
|
137
147
|
if (changed) onToggle(changed.value, next.includes(changed.value));
|
|
138
148
|
},
|
|
139
|
-
children:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
children: [
|
|
150
|
+
searchable ? /*#__PURE__*/ _jsx(SearchInput, {
|
|
151
|
+
id: `${id}-search`,
|
|
152
|
+
label: C.searchGoals,
|
|
153
|
+
value: query,
|
|
154
|
+
onChange: (event)=>setQuery(event.target.value)
|
|
155
|
+
}) : null,
|
|
156
|
+
groups ? groups.map((group, index)=>{
|
|
157
|
+
const items = group.options.filter((option)=>visible.some((item)=>item.value === option.value));
|
|
158
|
+
return items.length ? /*#__PURE__*/ _jsxs("div", {
|
|
159
|
+
children: [
|
|
160
|
+
group.label ? /*#__PURE__*/ _jsx("h3", {
|
|
161
|
+
children: group.label
|
|
162
|
+
}) : null,
|
|
163
|
+
/*#__PURE__*/ _jsx("ul", {
|
|
164
|
+
children: items.map((option)=>/*#__PURE__*/ _jsx("li", {
|
|
165
|
+
children: /*#__PURE__*/ _jsx(Checkbox, {
|
|
166
|
+
id: `${id}-${index}-${option.value}`,
|
|
167
|
+
label: option.label,
|
|
168
|
+
checked: value.includes(option.value),
|
|
169
|
+
onChange: (event)=>onToggle(option.value, event.target.checked)
|
|
170
|
+
})
|
|
171
|
+
}, option.value))
|
|
172
|
+
})
|
|
173
|
+
]
|
|
174
|
+
}, index) : null;
|
|
175
|
+
}) : null
|
|
176
|
+
]
|
|
145
177
|
});
|
|
146
178
|
}
|
|
147
179
|
export function TestName({ test }) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { SectionId } from '../contracts.js';
|
|
2
2
|
/** Open disclosures before scrolling, then give keyboard users a meaningful landing. */
|
|
3
|
-
export declare function jumpToSection(section: SectionId): void;
|
|
3
|
+
export declare function jumpToSection(section: SectionId, arm?: string): void;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
/** Open disclosures before scrolling, then give keyboard users a meaningful landing. */ export function jumpToSection(section) {
|
|
1
|
+
/** Open disclosures before scrolling, then give keyboard users a meaningful landing. */ export function jumpToSection(section, arm) {
|
|
2
2
|
const target = document.getElementById(`section-${section}`);
|
|
3
3
|
if (target instanceof HTMLDetailsElement) target.open = true;
|
|
4
|
-
|
|
4
|
+
const row = arm ? document.getElementById(`xp-version-${arm}`) : null;
|
|
5
|
+
(row ?? target)?.scrollIntoView?.({
|
|
5
6
|
block: 'start'
|
|
6
7
|
});
|
|
7
|
-
document.getElementById(`title-${section}`)?.focus({
|
|
8
|
+
(row ?? document.getElementById(`title-${section}`))?.focus({
|
|
8
9
|
preventScroll: true
|
|
9
10
|
});
|
|
10
11
|
}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import type { KindParts } from '../contracts.js';
|
|
1
|
+
import type { KindParts, SectionProps } from '../contracts.js';
|
|
2
|
+
export declare function VersionContent({ test, surface, armIndex }: SectionProps & {
|
|
3
|
+
armIndex: number;
|
|
4
|
+
}): import("react").JSX.Element | null;
|
|
2
5
|
export declare const pageParts: KindParts;
|
|
@@ -1,15 +1,92 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { TEST_PAGE as C } from '../../copy/testPage.js';
|
|
4
4
|
import { pageTargetIssue, setPageTarget, showsPageTarget } from '../../arms/pageTarget.model.js';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { CopyRoute } from '../pagePicker/CopyRoute.js';
|
|
7
|
+
import { PagePicker } from '../pagePicker/PagePicker.js';
|
|
8
|
+
import { testPageCollection } from '../pagePicker/pagePicker.model.js';
|
|
9
|
+
import { PAGE_COPY as P } from '../../copy/pages.js';
|
|
5
10
|
import { FieldErrors, reasonFor, save, TextEdit } from './fields.js';
|
|
6
|
-
function VersionContent({ test, armIndex }) {
|
|
11
|
+
export function VersionContent({ test, surface, armIndex }) {
|
|
12
|
+
const [open, setOpen] = useState(false);
|
|
7
13
|
const arm = test.doc?.arms[armIndex];
|
|
8
14
|
if (!arm || !test.doc) return null;
|
|
9
15
|
if (!showsPageTarget(test.doc.kind, arm.kind)) return /*#__PURE__*/ _jsx("span", {
|
|
10
16
|
className: "xp-content-chip",
|
|
11
17
|
children: C.holdoutContent
|
|
12
18
|
});
|
|
19
|
+
const collection = testPageCollection(surface.pageCollections, test.doc, test.preflight?.pagePlacements);
|
|
20
|
+
const placement = test.preflight?.pagePlacements?.find((entry)=>entry.arm === arm.key);
|
|
21
|
+
if (collection) {
|
|
22
|
+
const picker = /*#__PURE__*/ _jsx(PagePicker, {
|
|
23
|
+
collection: collection,
|
|
24
|
+
test: test,
|
|
25
|
+
armKey: arm.key,
|
|
26
|
+
apiRoute: surface.apiRoute,
|
|
27
|
+
onChoose: ()=>setOpen(false)
|
|
28
|
+
});
|
|
29
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
30
|
+
className: "xp-version-content xp-version-content--page",
|
|
31
|
+
children: [
|
|
32
|
+
/*#__PURE__*/ _jsx("strong", {
|
|
33
|
+
children: P.where
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ _jsx("p", {
|
|
36
|
+
children: placement?.id != null ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
37
|
+
children: [
|
|
38
|
+
placement.title,
|
|
39
|
+
" · ",
|
|
40
|
+
placement.path,
|
|
41
|
+
!placement.published ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
42
|
+
children: [
|
|
43
|
+
" · ",
|
|
44
|
+
P.draft
|
|
45
|
+
]
|
|
46
|
+
}) : null
|
|
47
|
+
]
|
|
48
|
+
}) : arm.pageTarget ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
49
|
+
children: [
|
|
50
|
+
arm.pageTarget,
|
|
51
|
+
" · ",
|
|
52
|
+
P.draft
|
|
53
|
+
]
|
|
54
|
+
}) : P.noPage
|
|
55
|
+
}),
|
|
56
|
+
placement?.id != null ? /*#__PURE__*/ _jsxs("div", {
|
|
57
|
+
children: [
|
|
58
|
+
placement.editorUrl ? /*#__PURE__*/ _jsx("a", {
|
|
59
|
+
href: placement.editorUrl,
|
|
60
|
+
target: "_blank",
|
|
61
|
+
rel: "noopener",
|
|
62
|
+
children: P.open
|
|
63
|
+
}) : null,
|
|
64
|
+
' ',
|
|
65
|
+
placement.url ? /*#__PURE__*/ _jsx("a", {
|
|
66
|
+
href: placement.url,
|
|
67
|
+
target: "_blank",
|
|
68
|
+
rel: "noopener",
|
|
69
|
+
children: P.preview
|
|
70
|
+
}) : null
|
|
71
|
+
]
|
|
72
|
+
}) : null,
|
|
73
|
+
/*#__PURE__*/ _jsx("button", {
|
|
74
|
+
type: "button",
|
|
75
|
+
disabled: !!reasonFor(test, 'arms'),
|
|
76
|
+
onClick: ()=>setOpen((value)=>!value),
|
|
77
|
+
children: open ? P.close : arm.pageTarget ? P.change : P.choose
|
|
78
|
+
}),
|
|
79
|
+
open ? arm.kind === 'variant' ? /*#__PURE__*/ _jsx(CopyRoute, {
|
|
80
|
+
collection: collection,
|
|
81
|
+
children: picker
|
|
82
|
+
}) : picker : null,
|
|
83
|
+
/*#__PURE__*/ _jsx(FieldErrors, {
|
|
84
|
+
test: test,
|
|
85
|
+
path: `arms.${armIndex}.pageTarget`
|
|
86
|
+
})
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
13
90
|
const issue = pageTargetIssue(arm.pageTarget, arm.kind);
|
|
14
91
|
return /*#__PURE__*/ _jsxs("div", {
|
|
15
92
|
className: "xp-version-content",
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
.xp-grid-field .xp-ui-field > label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
16
16
|
.xp-version-content { display: flex; flex-wrap: nowrap; gap: var(--xp-space-2); align-items: center; min-width: 0; }
|
|
17
17
|
.xp-version-content .xp-ui-button { white-space: nowrap; }
|
|
18
|
+
.xp-version-content--page { flex-direction: column; align-items: stretch; gap: var(--xp-space-3); }
|
|
19
|
+
.xp-version-content--page > button { align-self: flex-start; }
|
|
20
|
+
.xp-page-picker input[type="search"] { width: 100%; }
|
|
21
|
+
.xp-page-picker ul { list-style: none; padding: 0; margin-block: var(--xp-space-3); }
|
|
22
|
+
.xp-page-picker li > button { display: block; width: 100%; text-align: left; }
|
|
23
|
+
.xp-page-picker li > p { color: var(--xp-text-2); font: var(--xp-text-small); }
|
|
24
|
+
.xp-copy-route ol { list-style: decimal; padding-inline-start: var(--xp-space-6); }
|
|
18
25
|
.xp-content-chip { flex-shrink: 0; display: inline-flex; width: fit-content; border-radius: var(--xp-radius-chip); padding: var(--xp-space-half) var(--xp-space-2); font: var(--xp-text-small); background: var(--xp-surface-2); color: var(--xp-text-2); }
|
|
19
26
|
.xp-content-chip.xp-content-ready { background: var(--xp-success-bg); color: var(--xp-success); }
|
|
20
27
|
.xp-content-chip.xp-content-empty { background: var(--xp-warning-bg); color: var(--xp-warning); }
|
|
@@ -66,7 +73,7 @@
|
|
|
66
73
|
.xp-readiness--bar { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--xp-space-2) var(--xp-space-4); padding-block: var(--xp-space-3); min-height: 64px; border: 0; border-radius: 0; }
|
|
67
74
|
.xp-readiness__state { display: flex; align-items: center; flex-wrap: wrap; gap: var(--xp-space-2); min-width: 0; }
|
|
68
75
|
.xp-readiness__state p, .xp-readiness--bar > p { margin: 0; }
|
|
69
|
-
.xp-readiness__state > p { font-weight: 500; }
|
|
76
|
+
.xp-readiness__state > p { font-weight: 500; white-space: pre-line; }
|
|
70
77
|
.xp-readiness--bar .xp-save-status, .xp-readiness__separator { color: var(--xp-text-3); font: var(--xp-text-small); }
|
|
71
78
|
.xp-readiness__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--xp-space-2); }
|
|
72
79
|
.xp-readiness--bar > .xp-readiness__actions { grid-column: 2; grid-row: 1 / span 3; justify-content: flex-end; }
|
|
@@ -9,7 +9,8 @@ const EDITABLE_FIELDS = [
|
|
|
9
9
|
'flagPoint',
|
|
10
10
|
'metrics',
|
|
11
11
|
'analysis',
|
|
12
|
-
'exposurePolicy'
|
|
12
|
+
'exposurePolicy',
|
|
13
|
+
'pageCollection'
|
|
13
14
|
];
|
|
14
15
|
/** Drop unknown and locked keys even when the caller is untyped. Groups replace whole groups. */ export function editablePatch(status, partial) {
|
|
15
16
|
return Object.fromEntries(EDITABLE_FIELDS.flatMap((field)=>Object.hasOwn(partial, field) && partial[field] !== undefined && isEditable(status, field) ? [
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import type { DocId } from '../../types/contracts.js';
|
|
1
|
+
import type { Arm, DocId } from '../../types/contracts.js';
|
|
2
2
|
import type { PreflightResult } from '../../types/endpoints.js';
|
|
3
3
|
import type { TestDocument, TestDocumentPatch } from './testDocument.model.js';
|
|
4
4
|
/** Section data only: staging never saves. Call commit on blur or a section action. */
|
|
5
|
-
export declare function useTestDocument(id: DocId, { apiRoute }: {
|
|
5
|
+
export declare function useTestDocument(id: DocId, { apiRoute, refetchPreflightOnFocus }: {
|
|
6
6
|
apiRoute: string;
|
|
7
|
+
refetchPreflightOnFocus?: boolean;
|
|
7
8
|
}): {
|
|
8
9
|
stage: (partial: TestDocumentPatch) => void;
|
|
9
10
|
commit: () => Promise<void>;
|
|
10
11
|
reload: () => Promise<void>;
|
|
11
12
|
refreshPreflight: () => Promise<void>;
|
|
13
|
+
choosePage: (arm: Arm["key"], page: {
|
|
14
|
+
id: DocId;
|
|
15
|
+
path: string;
|
|
16
|
+
}) => Promise<void>;
|
|
17
|
+
chosenPages: Partial<Record<Arm["key"], {
|
|
18
|
+
id: DocId;
|
|
19
|
+
path: string;
|
|
20
|
+
}>>;
|
|
12
21
|
doc: TestDocument | null;
|
|
13
22
|
preflight: PreflightResult | null;
|
|
14
23
|
preflightError: string | null;
|
|
@@ -7,6 +7,7 @@ import { changedPatch, editablePatch, payloadFieldErrors } from './testDocument.
|
|
|
7
7
|
// Saves outlive their mounted reader. Reopening the same document must wait for its queue.
|
|
8
8
|
const drainingSaves = new Map();
|
|
9
9
|
const initialView = ()=>({
|
|
10
|
+
chosenPages: {},
|
|
10
11
|
doc: null,
|
|
11
12
|
preflight: null,
|
|
12
13
|
preflightError: null,
|
|
@@ -55,6 +56,7 @@ function stage(session, partial) {
|
|
|
55
56
|
if (!session.saved || session.view.stale || session.reloading || session.view.loadState !== 'ready' || session.controller.signal.aborted) return;
|
|
56
57
|
const patch = editablePatch(session.saved.status, partial);
|
|
57
58
|
if (!Object.keys(patch).length) return;
|
|
59
|
+
if (patch.pageCollection !== undefined && patch.pageCollection !== optimistic(session)?.pageCollection) session.view.chosenPages = {};
|
|
58
60
|
session.pending = {
|
|
59
61
|
...session.pending,
|
|
60
62
|
...patch
|
|
@@ -63,7 +65,7 @@ function stage(session, partial) {
|
|
|
63
65
|
if (!session.queue.length) session.view.saving = 'idle';
|
|
64
66
|
session.publish();
|
|
65
67
|
}
|
|
66
|
-
/** Section data only: staging never saves. Call commit on blur or a section action. */ export function useTestDocument(id, { apiRoute }) {
|
|
68
|
+
/** Section data only: staging never saves. Call commit on blur or a section action. */ export function useTestDocument(id, { apiRoute, refetchPreflightOnFocus = false }) {
|
|
67
69
|
const base = apiRoute.replace(/\/$/, '');
|
|
68
70
|
const documentId = String(id);
|
|
69
71
|
const key = JSON.stringify([
|
|
@@ -90,11 +92,12 @@ function stage(session, partial) {
|
|
|
90
92
|
controller,
|
|
91
93
|
reloading: false,
|
|
92
94
|
preflightReads: 0,
|
|
95
|
+
preflightLoading: false,
|
|
93
96
|
publish () {
|
|
94
97
|
if (controller.signal.aborted) return;
|
|
95
98
|
session.view.doc = session.view.loadState === 'ready' ? optimistic(session) : null;
|
|
96
99
|
session.view.hasUnsaved = session.queue.length > 0 || Object.keys(session.pending).length > 0;
|
|
97
|
-
session.view.preflightPending = session.preflightReads > 0;
|
|
100
|
+
session.view.preflightPending = session.preflightReads > 0 || session.preflightLoading;
|
|
98
101
|
setState({
|
|
99
102
|
key,
|
|
100
103
|
view: {
|
|
@@ -109,7 +112,7 @@ function stage(session, partial) {
|
|
|
109
112
|
async function refreshPreflight(initial = false) {
|
|
110
113
|
if (controller.signal.aborted) return;
|
|
111
114
|
const revision = ++preflightRevision;
|
|
112
|
-
session.
|
|
115
|
+
session.preflightLoading = true;
|
|
113
116
|
session.publish();
|
|
114
117
|
// createApi has no signal option; this read must abort without cancelling submitted PATCHes.
|
|
115
118
|
let data;
|
|
@@ -134,12 +137,9 @@ function stage(session, partial) {
|
|
|
134
137
|
} catch {
|
|
135
138
|
message = API_MESSAGES.network;
|
|
136
139
|
}
|
|
137
|
-
session.preflightReads--;
|
|
138
140
|
if (controller.signal.aborted) return;
|
|
139
|
-
if (revision !== preflightRevision)
|
|
140
|
-
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
141
|
+
if (revision !== preflightRevision) return;
|
|
142
|
+
session.preflightLoading = false;
|
|
143
143
|
if (data) {
|
|
144
144
|
session.view.preflight = data;
|
|
145
145
|
session.view.preflightError = null;
|
|
@@ -257,12 +257,9 @@ function stage(session, partial) {
|
|
|
257
257
|
session.publish();
|
|
258
258
|
}
|
|
259
259
|
if (saved) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
session.preflightReads--;
|
|
264
|
-
session.publish();
|
|
265
|
-
}
|
|
260
|
+
// Recovery is complete; readiness now belongs to the latest preflight read.
|
|
261
|
+
session.preflightReads--;
|
|
262
|
+
await refreshPreflight();
|
|
266
263
|
}
|
|
267
264
|
};
|
|
268
265
|
// Recover both the preceding tail and this step: unexpected throws must not strand later saves.
|
|
@@ -367,11 +364,52 @@ function stage(session, partial) {
|
|
|
367
364
|
}, [
|
|
368
365
|
key
|
|
369
366
|
]);
|
|
367
|
+
useEffect(()=>{
|
|
368
|
+
if (!refetchPreflightOnFocus) return;
|
|
369
|
+
let lastReturn = -Infinity;
|
|
370
|
+
const focus = ()=>{
|
|
371
|
+
if (document.visibilityState !== 'visible' || Date.now() - lastReturn < 100) return;
|
|
372
|
+
lastReturn = Date.now();
|
|
373
|
+
void refreshPreflight();
|
|
374
|
+
};
|
|
375
|
+
const visible = ()=>{
|
|
376
|
+
if (document.visibilityState === 'visible') focus();
|
|
377
|
+
};
|
|
378
|
+
window.addEventListener('focus', focus);
|
|
379
|
+
document.addEventListener('visibilitychange', visible);
|
|
380
|
+
return ()=>{
|
|
381
|
+
window.removeEventListener('focus', focus);
|
|
382
|
+
document.removeEventListener('visibilitychange', visible);
|
|
383
|
+
};
|
|
384
|
+
}, [
|
|
385
|
+
refetchPreflightOnFocus,
|
|
386
|
+
refreshPreflight
|
|
387
|
+
]);
|
|
388
|
+
const choosePage = useCallback((arm, page)=>{
|
|
389
|
+
const session = current.current;
|
|
390
|
+
const doc = session?.key === key ? optimistic(session) : null;
|
|
391
|
+
if (!session || !doc) return Promise.resolve();
|
|
392
|
+
session.view.chosenPages = {
|
|
393
|
+
...session.view.chosenPages,
|
|
394
|
+
[arm]: page
|
|
395
|
+
};
|
|
396
|
+
stage(session, {
|
|
397
|
+
arms: doc.arms.map((item)=>item.key === arm ? {
|
|
398
|
+
...item,
|
|
399
|
+
pageTarget: page.path
|
|
400
|
+
} : item)
|
|
401
|
+
});
|
|
402
|
+
return commit();
|
|
403
|
+
}, [
|
|
404
|
+
key,
|
|
405
|
+
commit
|
|
406
|
+
]);
|
|
370
407
|
return {
|
|
371
408
|
...state.key === key ? state.view : initialView(),
|
|
372
409
|
stage: stagePartial,
|
|
373
410
|
commit,
|
|
374
411
|
reload,
|
|
375
|
-
refreshPreflight
|
|
412
|
+
refreshPreflight,
|
|
413
|
+
choosePage
|
|
376
414
|
};
|
|
377
415
|
}
|
package/dist/admin/useApi.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
export declare function useApi(): {
|
|
1
|
+
export declare function useApi(apiRoute: string): {
|
|
2
|
+
wrapBlock: (body: import("../types/endpoints.js").WrapBlockBody) => Promise<import("./api.js").ApiResult<Record<string, unknown>>>;
|
|
3
|
+
pages: (query: {
|
|
4
|
+
collection: string;
|
|
5
|
+
q?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
page?: number;
|
|
8
|
+
}, signal?: AbortSignal) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").PagesResult>>;
|
|
9
|
+
placementStatus: (body: import("../types/endpoints.js").PlacementStatusBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").PlacementStatusResult>>;
|
|
2
10
|
start: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
|
|
3
11
|
allocate: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").AllocateBody, options?: {
|
|
4
12
|
notFound?: string;
|
|
@@ -9,7 +17,7 @@ export declare function useApi(): {
|
|
|
9
17
|
notFound?: string;
|
|
10
18
|
codes?: Readonly<Record<string, string>>;
|
|
11
19
|
fallback?: string;
|
|
12
|
-
}) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").
|
|
20
|
+
}) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").AddMetricResult>>;
|
|
13
21
|
stop: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
|
|
14
22
|
decide: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").DecideBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
|
|
15
23
|
releaseHoldout: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
|
|
@@ -18,6 +26,7 @@ export declare function useApi(): {
|
|
|
18
26
|
dismissContentEntry: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").DismissContentEntryBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").DismissContentEntryResult>>;
|
|
19
27
|
restoreContentEntry: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").RestoreContentEntryBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").RestoreContentEntryResult>>;
|
|
20
28
|
archive: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").ArchiveBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
|
|
29
|
+
clickKeys: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").ClickKeysResult>>;
|
|
21
30
|
preflight: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").PreflightResult>>;
|
|
22
31
|
previewToken: (id: import("../types/contracts.js").DocId, arm: string) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").PreviewTokenResult>>;
|
|
23
32
|
results: (id: import("../types/contracts.js").DocId, options?: {
|
package/dist/admin/useApi.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
|
-
import { useConfig } from '@payloadcms/ui';
|
|
4
3
|
import { createApi } from './api.js';
|
|
5
|
-
export function useApi() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
config.routes.api
|
|
4
|
+
export function useApi(apiRoute) {
|
|
5
|
+
return useMemo(()=>createApi(apiRoute), [
|
|
6
|
+
apiRoute
|
|
9
7
|
]);
|
|
10
8
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { accentStyles } from '../../ui/accent.js';
|
|
3
|
-
import { formatAdminURL } from 'payload/shared';
|
|
3
|
+
import { flattenTopLevelFields, formatAdminURL } from 'payload/shared';
|
|
4
4
|
import { notFound, redirect } from 'next/navigation.js';
|
|
5
5
|
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent';
|
|
6
6
|
import { serializeAttributeKeys, serializeFlagPoints, serializeMetricSources } from '../../collections/serialize.js';
|
|
7
|
-
import {
|
|
7
|
+
import { serializePageCollections } from '../pageCollections.js';
|
|
8
|
+
import { collectionLabel as resolveLabel } from '../../collections/labels.js';
|
|
9
|
+
import { documentEditor, puckCollections } from '../../options.js';
|
|
8
10
|
import { xpOptionsFromReq } from '../../options/fromReq.js';
|
|
9
11
|
import { XP_TESTS_SURFACE } from '../paths.js';
|
|
10
12
|
import { SURFACE_COPY } from '../copy/surface.js';
|
|
@@ -26,6 +28,9 @@ export async function ExperimentsRootView({ initPageResult, params, searchParams
|
|
|
26
28
|
} else if (tail.length === 1) {
|
|
27
29
|
route = 'test';
|
|
28
30
|
id = tail[0];
|
|
31
|
+
} else if (tail.length === 2 && tail[0] === 'by-slug') {
|
|
32
|
+
route = 'test';
|
|
33
|
+
id = tail[1];
|
|
29
34
|
} else if (tail.length === 2 && tail[1] === 'results') {
|
|
30
35
|
route = 'results';
|
|
31
36
|
id = tail[0];
|
|
@@ -71,6 +76,22 @@ export async function ExperimentsRootView({ initPageResult, params, searchParams
|
|
|
71
76
|
children: SURFACE_COPY.noAccess
|
|
72
77
|
});
|
|
73
78
|
}
|
|
79
|
+
if (canManage && id && tail[0] === 'by-slug') {
|
|
80
|
+
const result = await req.payload.find({
|
|
81
|
+
collection: 'xp-experiments',
|
|
82
|
+
req,
|
|
83
|
+
overrideAccess: false,
|
|
84
|
+
depth: 0,
|
|
85
|
+
limit: 1,
|
|
86
|
+
where: {
|
|
87
|
+
slug: {
|
|
88
|
+
equals: id
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
if (!result.docs[0]) return notFound();
|
|
93
|
+
id = String(result.docs[0].id);
|
|
94
|
+
}
|
|
74
95
|
const flagPoints = serializeFlagPoints(options);
|
|
75
96
|
// xp-state is manager-readable only. Results-only readers do not edit settings.
|
|
76
97
|
if (canManage && flagPoints.length) {
|
|
@@ -83,20 +104,22 @@ export async function ExperimentsRootView({ initPageResult, params, searchParams
|
|
|
83
104
|
const defaults = state.flagDefaults;
|
|
84
105
|
for (const point of flagPoints)point.effectiveDefaults = defaults?.[point.id]?.params ?? point.defaultParams;
|
|
85
106
|
}
|
|
86
|
-
const registrations =
|
|
87
|
-
|
|
107
|
+
const registrations = [
|
|
108
|
+
...new Set([
|
|
109
|
+
...options.puck?.enabled ? puckCollections(options) : [],
|
|
110
|
+
...options.blocks?.collections ?? []
|
|
111
|
+
].map((entry)=>entry.slug))
|
|
112
|
+
];
|
|
113
|
+
const pages = route === 'new' && canManage && registrations.length ? (await Promise.all(registrations.map(async (collection)=>{
|
|
88
114
|
try {
|
|
89
115
|
const config = req.payload.config.collections.find((item)=>item.slug === collection);
|
|
90
116
|
const useAsTitle = config?.admin.useAsTitle ?? 'id';
|
|
91
|
-
const
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
byLanguage.en,
|
|
98
|
-
...Object.values(byLanguage)
|
|
99
|
-
].find((value)=>typeof value === 'string') ?? collection;
|
|
117
|
+
const collectionLabel = resolveLabel(config?.labels.plural, collection, req);
|
|
118
|
+
// appliesTo may inspect any stored field; ordinary Puck lists keep their title-only projection.
|
|
119
|
+
const predicateFields = options.blocks?.collections.some((entry)=>entry.slug === collection) ? Object.fromEntries(flattenTopLevelFields(config?.fields ?? []).map((field)=>[
|
|
120
|
+
field.name,
|
|
121
|
+
true
|
|
122
|
+
])) : {};
|
|
100
123
|
const result = await req.payload.find({
|
|
101
124
|
collection,
|
|
102
125
|
req,
|
|
@@ -106,28 +129,35 @@ export async function ExperimentsRootView({ initPageResult, params, searchParams
|
|
|
106
129
|
sort: '-updatedAt',
|
|
107
130
|
draft: true,
|
|
108
131
|
select: {
|
|
109
|
-
[useAsTitle]: true
|
|
132
|
+
[useAsTitle]: true,
|
|
133
|
+
...predicateFields
|
|
110
134
|
}
|
|
111
135
|
});
|
|
136
|
+
// The new-test list uses the latest draft's editing intent, as the "Test a block" field condition does (§4.1).
|
|
112
137
|
return result.docs.map((doc)=>{
|
|
138
|
+
const editor = documentEditor(options, collection, doc);
|
|
139
|
+
if (editor !== 'puck' && editor !== 'blocks') return null;
|
|
113
140
|
const id = doc.id;
|
|
114
141
|
const customUrl = options.admin?.editorUrl?.({
|
|
115
142
|
collection,
|
|
116
143
|
id,
|
|
117
|
-
draft: true
|
|
144
|
+
draft: true,
|
|
145
|
+
editor,
|
|
146
|
+
doc
|
|
118
147
|
});
|
|
119
148
|
const title = doc[useAsTitle];
|
|
120
149
|
return {
|
|
121
150
|
collection,
|
|
122
151
|
collectionLabel,
|
|
123
152
|
id,
|
|
153
|
+
editor,
|
|
124
154
|
title: typeof title === 'string' || typeof title === 'number' ? String(title) : String(id),
|
|
125
155
|
editorUrl: typeof customUrl === 'string' ? customUrl : formatAdminURL({
|
|
126
156
|
adminRoute,
|
|
127
157
|
path: `/collections/${collection}/${encodeURIComponent(String(id))}`
|
|
128
158
|
})
|
|
129
159
|
};
|
|
130
|
-
});
|
|
160
|
+
}).filter((page)=>page !== null);
|
|
131
161
|
} catch {
|
|
132
162
|
return [];
|
|
133
163
|
}
|
|
@@ -151,6 +181,18 @@ export async function ExperimentsRootView({ initPageResult, params, searchParams
|
|
|
151
181
|
...pages === undefined ? {} : {
|
|
152
182
|
pages
|
|
153
183
|
},
|
|
184
|
+
pageCollections: serializePageCollections(options.pages?.collections ?? [], req),
|
|
185
|
+
collectionTitles: Object.fromEntries(req.payload.config.collections.flatMap((collection)=>{
|
|
186
|
+
const title = collection.admin.useAsTitle;
|
|
187
|
+
return title && title !== 'id' ? [
|
|
188
|
+
[
|
|
189
|
+
collection.slug,
|
|
190
|
+
title
|
|
191
|
+
]
|
|
192
|
+
] : [];
|
|
193
|
+
})),
|
|
194
|
+
userId: req.user.id,
|
|
195
|
+
userCollection: req.payload.config.admin.user,
|
|
154
196
|
attributeKeys: serializeAttributeKeys(options),
|
|
155
197
|
metricSources: serializeMetricSources(options),
|
|
156
198
|
...descriptions ? {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ResolvedOptions } from '../types/contracts.js';
|
|
2
|
+
/** Same document-kind rule as the scan; Payload blocks are top level, Puck is recursive. */
|
|
3
|
+
export declare function placementSlugs(options: Pick<ResolvedOptions, 'blocks' | 'puck'>, collection: string, doc: Record<string, unknown> | undefined): string[];
|