@delmaredigital/payload-experiments 0.3.0-rc.1 → 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 +172 -21
- 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 +47 -26
- package/dist/admin/TestsNavLink.d.ts +3 -1
- package/dist/admin/TestsNavLink.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/armSelect/ArmSelectField.js +14 -22
- package/dist/admin/arms/arms.model.d.ts +1 -21
- package/dist/admin/arms/arms.model.js +1 -153
- 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/arms.d.ts +1 -6
- package/dist/admin/copy/arms.js +1 -16
- 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 +25 -3
- package/dist/admin/copy/readiness.js +25 -3
- package/dist/admin/copy/results.d.ts +3 -0
- package/dist/admin/copy/results.js +4 -1
- package/dist/admin/copy/review.d.ts +1 -1
- package/dist/admin/copy/review.js +1 -1
- package/dist/admin/copy/surface.d.ts +0 -1
- package/dist/admin/copy/surface.js +0 -1
- 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 +8 -1
- package/dist/admin/paths.js +8 -1
- 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/previewLinks.model.d.ts +2 -0
- package/dist/admin/previewLinks.model.js +3 -0
- package/dist/admin/results/DataQualityPanel.js +4 -1
- package/dist/admin/results/legacy.d.ts +16 -0
- package/dist/admin/results/legacy.js +27 -0
- package/dist/admin/results/results.model.d.ts +9 -6
- package/dist/admin/results/results.model.js +22 -9
- package/dist/admin/results/snapshot.d.ts +14 -0
- package/dist/admin/results/snapshot.js +3 -0
- package/dist/admin/surface/TestsHome.js +43 -15
- 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 +19 -16
- 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 +2 -1
- package/dist/admin/surface/home.model.js +8 -4
- package/dist/admin/surface/newTest/NewTest.js +58 -17
- 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 +103 -69
- 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 +95 -6
- 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 +23 -3
- package/dist/admin/surface/results/ResultsHeader.js +2 -5
- package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
- package/dist/admin/surface/results/recommendation.model.js +10 -3
- package/dist/admin/surface/results/useResults.d.ts +10 -1
- package/dist/admin/surface/surface.css +0 -1
- package/dist/admin/surface/test/AdvancedSection.js +111 -104
- package/dist/admin/surface/test/GoalSection.js +97 -22
- 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/test/testPage.model.d.ts +2 -3
- package/dist/admin/surface/test/testPage.model.js +1 -21
- 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 +55 -16
- package/dist/admin/useApi.d.ts +11 -2
- package/dist/admin/useApi.js +3 -5
- package/dist/admin/useLayerUsage.js +14 -23
- package/dist/admin/views/ExperimentsRootView.js +58 -16
- package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
- package/dist/admin/views/TestsNavLinkServer.js +43 -0
- package/dist/arms/copy.d.ts +6 -0
- package/dist/arms/copy.js +16 -0
- package/dist/arms/model.d.ts +23 -0
- package/dist/arms/model.js +173 -0
- 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/appends.d.ts +6 -2
- package/dist/db/appends.js +10 -4
- 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 +14 -0
- package/dist/db/locks.js +36 -1
- 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/pageOccupancy.d.ts +12 -0
- package/dist/db/pageOccupancy.js +45 -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 +9 -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/adminRsc.d.ts +1 -0
- package/dist/exports/adminRsc.js +1 -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 +5 -2
- package/dist/exports/puck.js +3 -1
- package/dist/exports/puckEditor.d.ts +6 -1
- package/dist/exports/puckEditor.js +4 -1
- package/dist/exports/rsc.d.ts +4 -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 +160 -80
- package/dist/lib/fetchAllDocs.d.ts +16 -0
- package/dist/lib/fetchAllDocs.js +56 -0
- package/dist/onInit.js +29 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.js +43 -10
- package/dist/plugin.js +45 -28
- package/dist/puck/ExperimentPicker.js +47 -16
- package/dist/puck/ExperimentSlot.d.ts +51 -1
- package/dist/puck/ExperimentSlot.js +23 -12
- package/dist/puck/copy.d.ts +6 -4
- package/dist/puck/copy.js +6 -4
- package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
- package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
- package/dist/puck/editor/ResultsReadout.d.ts +6 -0
- package/dist/puck/editor/ResultsReadout.js +99 -0
- package/dist/puck/editor/VersionBuilder.d.ts +34 -0
- package/dist/puck/editor/VersionBuilder.js +317 -0
- package/dist/puck/editor/armWrites.d.ts +7 -0
- package/dist/puck/editor/armWrites.js +71 -0
- package/dist/puck/editor/armsCache.d.ts +14 -0
- package/dist/puck/editor/armsCache.js +78 -0
- package/dist/puck/editor/copy.d.ts +33 -1
- package/dist/puck/editor/copy.js +45 -8
- package/dist/puck/editor/createTest.d.ts +1 -1
- package/dist/puck/editor/createTest.js +25 -14
- package/dist/puck/editor/editor.chrome.css +48 -161
- package/dist/puck/editor/editor.css +48 -161
- package/dist/puck/editor/editor.model.d.ts +8 -3
- package/dist/puck/editor/editor.model.js +16 -10
- package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
- package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
- 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/experimentSlotField.d.ts +2 -0
- package/dist/puck/experimentSlotField.js +11 -3
- package/dist/puck/hostContext.d.ts +10 -0
- package/dist/puck/hostContext.js +12 -0
- package/dist/puck/integration.d.ts +6 -0
- package/dist/puck/integration.js +21 -0
- package/dist/puck/routes.d.ts +12 -0
- package/dist/puck/routes.js +11 -0
- package/dist/puck/slotConfig.d.ts +1 -1
- package/dist/puck/slotConfig.js +2 -2
- 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 +51 -1
- package/dist/server/metadata.js +9 -2
- package/dist/server/normaliseContentRelationships.d.ts +7 -0
- package/dist/server/normaliseContentRelationships.js +62 -0
- package/dist/server/pageKindSeoContract.d.ts +27 -0
- package/dist/server/pageKindSeoContract.js +80 -0
- package/dist/server/recordContentChange.d.ts +1 -1
- package/dist/server/recordContentChange.js +72 -13
- package/dist/server/resolveExperimentBlocks.d.ts +5 -0
- package/dist/server/resolveExperimentBlocks.js +54 -0
- package/dist/server/runResults.js +92 -19
- 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 +17 -1
- package/dist/transitions/checkStart.js +99 -7
- package/dist/transitions/decide.js +40 -1
- 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 +10 -6
- package/dist/transitions/placements.js +156 -2
- package/dist/transitions/preflightData.d.ts +8 -0
- package/dist/transitions/preflightData.js +248 -36
- package/dist/transitions/resolveCaps.d.ts +1 -0
- package/dist/transitions/resolveCaps.js +6 -2
- package/dist/transitions/start.js +77 -3
- package/dist/types/admin.d.ts +1 -1
- package/dist/types/assignments.d.ts +1 -0
- package/dist/types/contracts.d.ts +31 -10
- 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/dist/ui/Tabs.d.ts +4 -2
- package/dist/ui/Tabs.js +33 -26
- package/dist/ui/accent.d.ts +22 -3
- package/dist/ui/accent.js +74 -16
- package/package.json +12 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { placementPathContent, placementTests } from '../db/placements.js';
|
|
2
|
+
import { lockPagePaths } from '../db/locks.js';
|
|
3
|
+
import { normalisePath } from '../runtime/normalisePath.js';
|
|
4
|
+
import { scanBlockPlacements } from './placements.js';
|
|
5
|
+
import { interactingTestsCopy } from '../admin/copy/readiness.js';
|
|
6
|
+
import { XpRefusal } from '../errors.js';
|
|
7
|
+
import { pageTargetPaths } from './checkStart.js';
|
|
8
|
+
export async function detached(req, fn) {
|
|
9
|
+
const transactionID = req.transactionID;
|
|
10
|
+
delete req.transactionID;
|
|
11
|
+
try {
|
|
12
|
+
return await fn();
|
|
13
|
+
} finally{
|
|
14
|
+
req.transactionID = transactionID;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function uniquePlacementDocs(placements) {
|
|
18
|
+
return [
|
|
19
|
+
...new Map(placements.map((p)=>[
|
|
20
|
+
JSON.stringify([
|
|
21
|
+
p.collection,
|
|
22
|
+
String(p.id)
|
|
23
|
+
]),
|
|
24
|
+
{
|
|
25
|
+
collection: p.collection,
|
|
26
|
+
id: p.id
|
|
27
|
+
}
|
|
28
|
+
])).values()
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
export function scanPlacements(req, options, slug) {
|
|
32
|
+
return scanBlockPlacements(req, {
|
|
33
|
+
slug,
|
|
34
|
+
puckCollections: options.puck?.collections ?? [],
|
|
35
|
+
blocksCollections: options.blocks?.collections ?? []
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function placementPaths(req, options, docs) {
|
|
39
|
+
const paths = new Set();
|
|
40
|
+
for (const doc of docs){
|
|
41
|
+
const registration = options.pages?.collections.find((r)=>r.slug === doc.collection);
|
|
42
|
+
if (!registration) continue;
|
|
43
|
+
const row = await placementPathContent(req, doc.collection, doc.id);
|
|
44
|
+
if (!row || row._status === 'draft') continue;
|
|
45
|
+
const path = registration.pathOf(row);
|
|
46
|
+
if (path !== null) paths.add(normalisePath(path));
|
|
47
|
+
}
|
|
48
|
+
return [
|
|
49
|
+
...paths
|
|
50
|
+
].sort();
|
|
51
|
+
}
|
|
52
|
+
const active = (test)=>test.status === 'running' || test.status === 'decided';
|
|
53
|
+
export async function placementInteractions(req, exec, options, experiment, paths) {
|
|
54
|
+
const result = [];
|
|
55
|
+
for (const test of (await placementTests(exec))){
|
|
56
|
+
if (!active(test) || String(test.id) === String(experiment.id)) continue;
|
|
57
|
+
if (experiment.kind === 'block' && test.kind === 'page') {
|
|
58
|
+
for (const path of paths.filter((path)=>pageTargetPaths(test.arms).includes(path)))result.push({
|
|
59
|
+
experimentId: test.id,
|
|
60
|
+
slug: test.slug,
|
|
61
|
+
name: test.name,
|
|
62
|
+
status: test.status,
|
|
63
|
+
path,
|
|
64
|
+
version: test.arms.some((a)=>a.kind === 'variant' && a.pageTarget && normalisePath(a.pageTarget) === path)
|
|
65
|
+
});
|
|
66
|
+
} else if (experiment.kind === 'page' && test.kind === 'block') {
|
|
67
|
+
const docs = test.placementDocs ?? uniquePlacementDocs(await detached(req, ()=>scanPlacements(req, options, test.slug)));
|
|
68
|
+
const relevant = docs.filter((doc)=>!experiment.pageCollection || doc.collection === experiment.pageCollection);
|
|
69
|
+
for (const path of (await placementPaths(req, options, relevant)).filter((path)=>paths.includes(path)))result.push({
|
|
70
|
+
experimentId: test.id,
|
|
71
|
+
slug: test.slug,
|
|
72
|
+
name: test.name,
|
|
73
|
+
status: test.status,
|
|
74
|
+
path,
|
|
75
|
+
version: experiment.arms.some((a)=>a.kind === 'variant' && a.pageTarget && normalisePath(a.pageTarget) === path)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
export async function lockAndRescanPlacements(req, exec, options, experiment, before) {
|
|
82
|
+
const paths = await placementPaths(req, options, uniquePlacementDocs(before));
|
|
83
|
+
await lockPagePaths(exec, paths);
|
|
84
|
+
const placements = await detached(req, ()=>scanPlacements(req, options, experiment.slug));
|
|
85
|
+
const docs = uniquePlacementDocs(placements);
|
|
86
|
+
const afterPaths = await placementPaths(req, options, docs);
|
|
87
|
+
if (afterPaths.some((path)=>!paths.includes(path))) throw new XpRefusal('placements-changed', "This test's pages changed while it was starting. Try again.", undefined, 409);
|
|
88
|
+
const conflicts = await placementInteractions(req, exec, options, experiment, paths);
|
|
89
|
+
if (conflicts.length) throw new XpRefusal('interacting-tests', interactingTestsCopy(conflicts[0].path, conflicts[0].status, conflicts[0].name, conflicts[0].version), conflicts, 409);
|
|
90
|
+
return {
|
|
91
|
+
placements,
|
|
92
|
+
docs
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { PayloadRequest } from 'payload';
|
|
2
|
-
import type { Arm, DocId, FlagPoint, ResolvedOptions } from '../types/contracts.js';
|
|
2
|
+
import type { Arm, DocId, FlagPoint, PreviewPlacement, ResolvedOptions } from '../types/contracts.js';
|
|
3
3
|
import type { ArmSlot } from '../types/puck.js';
|
|
4
|
-
export interface BlockPlacement {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
title: string;
|
|
8
|
-
published: boolean;
|
|
4
|
+
export interface BlockPlacement extends PreviewPlacement {
|
|
5
|
+
editor?: 'puck' | 'blocks' | 'page';
|
|
6
|
+
doc?: Record<string, unknown>;
|
|
9
7
|
slots: Record<ArmSlot, number>;
|
|
8
|
+
duplicateRows?: boolean;
|
|
9
|
+
identicalSlots?: string[];
|
|
10
10
|
locked?: boolean;
|
|
11
11
|
localized?: boolean;
|
|
12
12
|
}
|
|
@@ -17,10 +17,14 @@ export type FlagPlacement = Awaited<ReturnType<NonNullable<FlagPoint['placements
|
|
|
17
17
|
export declare function scanBlockPlacements(req: PayloadRequest, args: {
|
|
18
18
|
slug: string;
|
|
19
19
|
puckCollections: NonNullable<ResolvedOptions['puck']>['collections'];
|
|
20
|
+
blocksCollections: NonNullable<ResolvedOptions['blocks']>['collections'];
|
|
21
|
+
compareVersions?: boolean;
|
|
20
22
|
}): Promise<BlockPlacement[]>;
|
|
21
23
|
export declare function scanBlockPlacements(req: PayloadRequest, args: {
|
|
22
24
|
slugs: ReadonlySet<string>;
|
|
23
25
|
puckCollections: NonNullable<ResolvedOptions['puck']>['collections'];
|
|
26
|
+
blocksCollections: NonNullable<ResolvedOptions['blocks']>['collections'];
|
|
27
|
+
compareVersions?: boolean;
|
|
24
28
|
}): Promise<Map<string, BlockPlacement[]>>;
|
|
25
29
|
export declare function flagPlacements<TUser extends {
|
|
26
30
|
id: DocId;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { BLOCK_SLOTS, EXPERIMENT_BLOCK_SLUG, hostBlocks, slotBlocks } from '../blocks/model.js';
|
|
2
|
+
import { safeError } from '../log.js';
|
|
3
|
+
import { pageVersionContent } from '../db/pageContent.js';
|
|
4
|
+
import { comparableContent } from './pageVersions.js';
|
|
5
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
1
6
|
import { hasLocalizedFields } from './contentFields.js';
|
|
2
|
-
import { puckCollections as normalisePuckCollections } from '../options.js';
|
|
7
|
+
import { documentEditor, puckCollections as normalisePuckCollections } from '../options.js';
|
|
3
8
|
const slots = [
|
|
4
9
|
'control',
|
|
5
10
|
'variantA',
|
|
@@ -23,7 +28,9 @@ export async function scanBlockPlacements(req, args) {
|
|
|
23
28
|
collections: puckCollections
|
|
24
29
|
}
|
|
25
30
|
})){
|
|
31
|
+
const editors = new Map();
|
|
26
32
|
const publishedIds = new Set();
|
|
33
|
+
const publishedSlugs = new Map();
|
|
27
34
|
const draftsEnabled = req.payload.config.collections?.some((config)=>config.slug === collection && config.versions && config.versions.drafts);
|
|
28
35
|
for (const draft of [
|
|
29
36
|
false,
|
|
@@ -51,7 +58,29 @@ export async function scanBlockPlacements(req, args) {
|
|
|
51
58
|
for (const doc of result.docs){
|
|
52
59
|
if (!draft && doc._status === 'draft') continue;
|
|
53
60
|
if (draft && doc._status !== 'draft' && publishedIds.has(String(doc.id))) continue;
|
|
54
|
-
if (!draft)
|
|
61
|
+
if (!draft) {
|
|
62
|
+
editors.set(String(doc.id), documentEditor({
|
|
63
|
+
puck: {
|
|
64
|
+
enabled: true,
|
|
65
|
+
collections: puckCollections
|
|
66
|
+
},
|
|
67
|
+
blocks: {
|
|
68
|
+
collections: args.blocksCollections
|
|
69
|
+
}
|
|
70
|
+
}, collection, doc));
|
|
71
|
+
publishedIds.add(String(doc.id));
|
|
72
|
+
if (typeof doc.slug === 'string') publishedSlugs.set(String(doc.id), doc.slug);
|
|
73
|
+
}
|
|
74
|
+
const editor = editors.get(String(doc.id)) ?? documentEditor({
|
|
75
|
+
puck: {
|
|
76
|
+
enabled: true,
|
|
77
|
+
collections: puckCollections
|
|
78
|
+
},
|
|
79
|
+
blocks: {
|
|
80
|
+
collections: args.blocksCollections
|
|
81
|
+
}
|
|
82
|
+
}, collection, doc);
|
|
83
|
+
if (editor !== 'puck') continue;
|
|
55
84
|
function walk(value) {
|
|
56
85
|
if (!value || typeof value !== 'object') return;
|
|
57
86
|
if (Array.isArray(value)) {
|
|
@@ -66,8 +95,13 @@ export async function scanBlockPlacements(req, args) {
|
|
|
66
95
|
placements.get(experiment).push({
|
|
67
96
|
collection,
|
|
68
97
|
id: doc.id,
|
|
98
|
+
editor: 'puck',
|
|
99
|
+
doc,
|
|
69
100
|
title: String(doc.title ?? doc.name ?? doc.id),
|
|
70
101
|
published: !draft || doc._status === 'published',
|
|
102
|
+
...publishedSlugs.has(String(doc.id)) ? {
|
|
103
|
+
slug: publishedSlugs.get(String(doc.id))
|
|
104
|
+
} : {},
|
|
71
105
|
slots: Object.fromEntries(slots.map((slot)=>[
|
|
72
106
|
slot,
|
|
73
107
|
Array.isArray(props[slot]) ? props[slot].length : 0
|
|
@@ -80,7 +114,115 @@ export async function scanBlockPlacements(req, args) {
|
|
|
80
114
|
}
|
|
81
115
|
Object.values(node).forEach(walk);
|
|
82
116
|
}
|
|
117
|
+
const offsets = new Map([
|
|
118
|
+
...placements
|
|
119
|
+
].map(([slug, list])=>[
|
|
120
|
+
slug,
|
|
121
|
+
list.length
|
|
122
|
+
]));
|
|
83
123
|
walk(doc[field]);
|
|
124
|
+
if (args.compareVersions && !draft && [
|
|
125
|
+
...placements
|
|
126
|
+
].some(([slug, list])=>list.length > offsets.get(slug))) {
|
|
127
|
+
const raw = await comparisonContent(req, collection, doc.id);
|
|
128
|
+
const rawSlots = new Map();
|
|
129
|
+
const collect = (value)=>{
|
|
130
|
+
if (Array.isArray(value)) {
|
|
131
|
+
value.forEach(collect);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!value || typeof value !== 'object') return;
|
|
135
|
+
const node = value, props = node.props;
|
|
136
|
+
const ref = props?.experiment;
|
|
137
|
+
const slug = typeof ref === 'string' ? ref : ref && typeof ref === 'object' && 'slug' in ref ? ref.slug : undefined;
|
|
138
|
+
if (node.type === 'ExperimentSlot' && props && typeof slug === 'string') {
|
|
139
|
+
const list = rawSlots.get(slug) ?? [];
|
|
140
|
+
list.push(props);
|
|
141
|
+
rawSlots.set(slug, list);
|
|
142
|
+
}
|
|
143
|
+
Object.values(node).forEach(collect);
|
|
144
|
+
};
|
|
145
|
+
if (raw && raw._status !== 'draft') collect(raw[field]);
|
|
146
|
+
for (const [slug, list] of placements)list.slice(offsets.get(slug)).forEach((placement, index)=>{
|
|
147
|
+
const props = rawSlots.get(slug)?.[index];
|
|
148
|
+
if (props) placement.identicalSlots = slots.filter((slot)=>slot !== 'control' && Array.isArray(props[slot]) && props[slot].length > 0 && isDeepStrictEqual(comparableContent(props.control ?? []), comparableContent(props[slot])));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!result.hasNextPage) break;
|
|
153
|
+
page++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const registration of args.blocksCollections){
|
|
158
|
+
const { slug: collection, field } = registration;
|
|
159
|
+
const published = new Map();
|
|
160
|
+
const draftsEnabled = req.payload.config.collections?.some((c)=>c.slug === collection && c.versions && c.versions.drafts);
|
|
161
|
+
for (const draft of [
|
|
162
|
+
false,
|
|
163
|
+
true
|
|
164
|
+
]){
|
|
165
|
+
let page = 1;
|
|
166
|
+
while(true){
|
|
167
|
+
const result = await req.payload.find({
|
|
168
|
+
collection,
|
|
169
|
+
req,
|
|
170
|
+
overrideAccess: false,
|
|
171
|
+
depth: 0,
|
|
172
|
+
limit: 100,
|
|
173
|
+
page,
|
|
174
|
+
draft,
|
|
175
|
+
includeLockStatus: true,
|
|
176
|
+
...!draft && draftsEnabled ? {
|
|
177
|
+
where: {
|
|
178
|
+
_status: {
|
|
179
|
+
equals: 'published'
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} : {}
|
|
183
|
+
});
|
|
184
|
+
for (const doc of result.docs){
|
|
185
|
+
if (!draft && doc._status === 'draft') continue;
|
|
186
|
+
if (!draft) published.set(String(doc.id), doc);
|
|
187
|
+
else if (doc._status !== 'draft' && published.has(String(doc.id))) continue;
|
|
188
|
+
const baseline = published.get(String(doc.id)) ?? doc;
|
|
189
|
+
if (documentEditor({
|
|
190
|
+
blocks: {
|
|
191
|
+
collections: args.blocksCollections
|
|
192
|
+
},
|
|
193
|
+
puck: {
|
|
194
|
+
enabled: true,
|
|
195
|
+
collections: puckCollections
|
|
196
|
+
}
|
|
197
|
+
}, collection, baseline) !== 'blocks') continue;
|
|
198
|
+
const raw = args.compareVersions && !draft && hostBlocks(doc[field]).some((block)=>block.blockType === (registration.blockSlug ?? EXPERIMENT_BLOCK_SLUG) && typeof block.experiment === 'string' && slugs.has(block.experiment)) ? await comparisonContent(req, collection, doc.id) : undefined;
|
|
199
|
+
for (const [index, block] of hostBlocks(doc[field]).entries()){
|
|
200
|
+
if (block.blockType !== (registration.blockSlug ?? EXPERIMENT_BLOCK_SLUG) || typeof block.experiment !== 'string' || !slugs.has(block.experiment)) continue;
|
|
201
|
+
const stored = raw ? hostBlocks(raw[field]).find((row)=>row.id === block.id) ?? hostBlocks(raw[field])[index] ?? block : block;
|
|
202
|
+
const rows = Array.isArray(stored.slots) ? stored.slots : [];
|
|
203
|
+
const keys = rows.map((row)=>row?.slot);
|
|
204
|
+
placements.get(block.experiment).push({
|
|
205
|
+
collection,
|
|
206
|
+
id: doc.id,
|
|
207
|
+
editor: 'blocks',
|
|
208
|
+
doc,
|
|
209
|
+
title: String(doc.title ?? doc.name ?? doc.id),
|
|
210
|
+
published: !draft || doc._status === 'published',
|
|
211
|
+
...typeof baseline.slug === 'string' ? {
|
|
212
|
+
slug: baseline.slug
|
|
213
|
+
} : {},
|
|
214
|
+
slots: Object.fromEntries(BLOCK_SLOTS.map((slot)=>[
|
|
215
|
+
slot,
|
|
216
|
+
slotBlocks(block, slot).length
|
|
217
|
+
])),
|
|
218
|
+
duplicateRows: new Set(keys).size !== keys.length,
|
|
219
|
+
identicalSlots: raw ? BLOCK_SLOTS.filter((slot)=>slot !== 'control' && slotBlocks(block, slot).length > 0 && isDeepStrictEqual(comparableContent(slotBlocks(stored, 'control')), comparableContent(slotBlocks(stored, slot)))) : [],
|
|
220
|
+
locked: doc._isLocked === true || doc.locked === true,
|
|
221
|
+
...hasLocalizedFields(req.payload.config.collections?.find((c)=>c.slug === collection)?.fields ?? [], req.payload.config) ? {
|
|
222
|
+
localized: true
|
|
223
|
+
} : {}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
84
226
|
}
|
|
85
227
|
if (!result.hasNextPage) break;
|
|
86
228
|
page++;
|
|
@@ -98,3 +240,15 @@ export async function flagPlacements(options, experiment, req) {
|
|
|
98
240
|
req
|
|
99
241
|
});
|
|
100
242
|
}
|
|
243
|
+
/** A warning-only comparison must never turn an adapter failure into a Start refusal. */ async function comparisonContent(req, collection, id) {
|
|
244
|
+
try {
|
|
245
|
+
return await pageVersionContent(req, collection, id);
|
|
246
|
+
} catch (cause) {
|
|
247
|
+
req.payload.logger.error({
|
|
248
|
+
err: safeError(cause),
|
|
249
|
+
collection,
|
|
250
|
+
id
|
|
251
|
+
}, 'Experiment version comparison failed');
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -13,6 +13,14 @@ export interface PreflightDependencies {
|
|
|
13
13
|
export declare function loadStartInputs<TUser extends {
|
|
14
14
|
id: DocId;
|
|
15
15
|
}>(req: PayloadRequest, options: ResolvedOptions<TUser>, experimentId: DocId): Promise<CheckStartInput | null>;
|
|
16
|
+
/** Occupancy from the arms the runtime routes on (frozen rounds), shared by preflight and
|
|
17
|
+
* Start's locked recheck. Raw SQL on the request's transaction when there is one. */
|
|
18
|
+
export declare function loadPageTestsInUse(req: PayloadRequest): Promise<NonNullable<CheckStartInput['pageTestsInUse']>>;
|
|
19
|
+
/** Page targets no registered collection reports as published. A lookup that throws counts as
|
|
20
|
+
* unpublished (logged) unless propagateErrors is requested by decide. Start calls it again under its page-path locks on a detached request. */
|
|
21
|
+
export declare function unpublishedPageTargets<TUser extends {
|
|
22
|
+
id: DocId;
|
|
23
|
+
}>(req: PayloadRequest, options: ResolvedOptions<TUser>, experiment: Pick<StartExperiment, 'id' | 'arms' | 'pageCollection'>, propagateErrors?: boolean): Promise<string[]>;
|
|
16
24
|
/** Shared, request-scoped reads for summary; a failed row is null in tolerant mode.
|
|
17
25
|
* Preflight/Start use the same builder and retain their throwing read behaviour.
|
|
18
26
|
*/
|