@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
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @delmaredigital/payload-experiments
|
|
2
2
|
|
|
3
|
+
> ⚠️ Upgrading to 0.4? Run `payload migrate:create` for four nullable plugin columns (`xp_experiments.page_collection` and its `_xp_experiments_v` counterpart, `xp_phases.placement_docs`, `xp_state.retired_slugs`), plus the Experiment block and `xpGoal` columns if you adopt them, then `payload generate:importmap`. Saves on pages, blocks and Puck collections can now be refused while a test runs on them (`page-busy`, `interacting-tests`); Puck tests whose slot is only on a draft can't start until the page is published; host `metricSources` ids may not start with `click:`. See the CHANGELOG's BREAKING section.
|
|
4
|
+
|
|
3
5
|
> ⚠️ Upgrading to 0.3? For 0.3.0-rc.1, every `linkIdentity` call must supply `verified`; it now returns an outcome. Run `payload migrate:create` for the nullable `xp_identities.verified_at` column and `user_id` index, plus the release-pages columns listed in the CHANGELOG (`decision_pages_released_at` / `decision_pages_released_by` on `xp_experiments` and their `version_` counterparts on `_xp_experiments_v`). Existing identity links become unverified; see the CHANGELOG for optional backfill.
|
|
4
6
|
|
|
5
7
|
> ⚠️ Upgrading to 0.2? After upgrading, run `payload generate:importmap`: the Tests surface is registered through admin.dependencies, and without it the Tests routes show a 'not installed' message instead of the Tests screen. In 0.2.0-rc.2, the Tests server views moved from `/rsc` to `/admin/rsc`, so the import map must be regenerated when upgrading from rc.1 too. The old experiments edit-form components are gone from `/admin/client`, and the `xp-experiments` admin list and edit views now redirect to the Tests surface.
|
|
@@ -271,7 +273,10 @@ Other server integrations:
|
|
|
271
273
|
- `recordContentChange({ doc, previousDoc, req, collection })` can be called
|
|
272
274
|
from a host collection's `afterChange` for content tagged with the arm-select
|
|
273
275
|
field; for a collection with drafts, also add `captureContentBaseline` to its
|
|
274
|
-
`beforeChange` hooks so the previously published content can be compared.
|
|
276
|
+
`beforeChange` hooks so the previously published content can be compared.
|
|
277
|
+
Collections without drafts can also add `captureContentBaseline` for an exact
|
|
278
|
+
raw comparison; without it, the helper normalises relationships to ids. No host
|
|
279
|
+
change is required for collections without drafts. Supply that hook's transaction request. Puck collections get the hook
|
|
275
280
|
automatically when enabled. Markers compare published content; drafts and
|
|
276
281
|
autosaves do not add markers (collections without drafts record on save).
|
|
277
282
|
Neither the helper nor the installed hooks ever throw into your save: a
|
|
@@ -281,7 +286,10 @@ Other server integrations:
|
|
|
281
286
|
canonical; archived losing paths point canonically to the winner. It reads
|
|
282
287
|
the signed request context via `headers()`, so it decides from the URL the
|
|
283
288
|
visitor requested, not the rewritten route, and the Original's URL is never
|
|
284
|
-
noindex.
|
|
289
|
+
noindex. While a test is live the Original's URL also gets a canonical to
|
|
290
|
+
itself, even when it is served with a variant's route: merge this result after
|
|
291
|
+
your own metadata so its `alternates.canonical` wins over one derived from the
|
|
292
|
+
rendered route. Call it only from a dynamic `generateMetadata`, never inside
|
|
285
293
|
`'use cache'`; `path` is the fallback when the proxy did not run.
|
|
286
294
|
`experimentSitemapFilter(path, manifest)` is a synchronous build-time filter:
|
|
287
295
|
pass a privately server-loaded manifest and keep paths for which it returns
|
|
@@ -420,6 +428,7 @@ schema. Optional callbacks integrate host-owned content:
|
|
|
420
428
|
|
|
421
429
|
A `MetricSource`, registered in `metricSources`, has `id`, `label`,
|
|
422
430
|
`kind: 'binomial' | 'count' | 'revenue'`, and `keyedBy: 'visitor' | 'user'`.
|
|
431
|
+
Source ids starting with `click:` are reserved for click goals and refused at boot.
|
|
423
432
|
`clientWritable` optionally permits client conversions (revenue is always
|
|
424
433
|
server-only). `query({ from, to, unitIds? })` returns a promise of
|
|
425
434
|
`[{ unitId, value, ts }]`, **one row per event**, with `ts` a `Date`; use the
|
|
@@ -450,6 +459,21 @@ are recorded with `trackConversion`. Beacon conversions for a non-writable name
|
|
|
450
459
|
are dropped silently. Only listed names become `events:<name>` sources. They
|
|
451
460
|
support visitor and user units and do not represent counts or revenue in v1.
|
|
452
461
|
|
|
462
|
+
### Testing page SEO in the host
|
|
463
|
+
|
|
464
|
+
`pageKindSeoContract({ secret, original, versions, state, winner? })` from `/rsc`
|
|
465
|
+
returns `{ manifest, rows }`. States are `running`, `decided` and `archived`;
|
|
466
|
+
provide a winner path for the latter two. Build the host's test runtime with
|
|
467
|
+
`loadManifest: async () => manifest` and the same secret. For each row, set
|
|
468
|
+
`next/headers` to its signed `headers`, render metadata for `servedPath`, and
|
|
469
|
+
assert `expect.robots`, `expect.canonical` and `expect.inSitemap` against the
|
|
470
|
+
host's metadata and sitemap functions. This includes the Original served as
|
|
471
|
+
each version, including the homepage.
|
|
472
|
+
|
|
473
|
+
A host-level test mocks four boundaries: `next/headers`, the page loader,
|
|
474
|
+
the runtime and the site URL. Set `metadataBase` so relative canonical paths
|
|
475
|
+
resolve to your site URL.
|
|
476
|
+
|
|
453
477
|
### Whole-page options
|
|
454
478
|
|
|
455
479
|
| Option | Purpose |
|
|
@@ -483,18 +507,39 @@ pages: {
|
|
|
483
507
|
Adapt both callbacks for the site's homepage path. These checks run before Start;
|
|
484
508
|
they do not protect pages from later deletion, unpublishing or renaming. Keep an
|
|
485
509
|
archived winner published at the same address: archived test URLs still point to it.
|
|
486
|
-
|
|
510
|
+
Whole-page registrations also install the sidebar test status and content-change hooks.
|
|
511
|
+
For 0.4.0, run `payload migrate:create` for `xp_state.retired_slugs`,
|
|
512
|
+
`xp_experiments.page_collection` and its version column, then
|
|
513
|
+
`payload generate:importmap`.
|
|
514
|
+
|
|
515
|
+
`contentOf(doc)` optionally selects the content your site renders (for example,
|
|
516
|
+
`doc.layout`). Published copies are compared with nested `id` and `blockName`
|
|
517
|
+
keys removed. Without it, content checks and page content-change comparisons
|
|
518
|
+
are skipped. `copyHint` (up to 200 characters), `copyUrl` (a root-relative path)
|
|
519
|
+
and `copyLabel` (up to 40 characters, requires `copyUrl`) customize the guidance
|
|
520
|
+
for making version pages. The default copy link opens the registered collection's
|
|
521
|
+
list under the configured admin route.
|
|
487
522
|
|
|
488
523
|
### `admin` options
|
|
489
524
|
|
|
490
525
|
Host links and kind descriptions are optional. For a host using payload-puck's
|
|
491
|
-
editor route and a `puck-templates` collection
|
|
526
|
+
editor route, public pages and a `puck-templates` collection:
|
|
527
|
+
|
|
528
|
+
| Option | Signature and behavior |
|
|
529
|
+
| --- | --- |
|
|
530
|
+
| `admin.previewUrl` | `(placement: PreviewPlacement) => string \| null \| undefined \| Promise<string \| null \| undefined>`. Receives `collection`, `id`, `title`, `published`, and optional `slug`. May be async; return `null` to hide preview for that placement. Each call has a 2 s limit, with at most 8 lookups awaited concurrently. |
|
|
531
|
+
|
|
532
|
+
For example, pass:
|
|
492
533
|
|
|
493
534
|
```ts
|
|
494
535
|
admin: {
|
|
495
|
-
editorUrl: ({ collection, id, draft }) =>
|
|
536
|
+
editorUrl: ({ collection, id, draft, editor, doc }) =>
|
|
496
537
|
`/admin/puck-editor/${encodeURIComponent(collection)}/${encodeURIComponent(String(id))}`,
|
|
497
|
-
previewUrl: ({
|
|
538
|
+
previewUrl: async ({ collection, slug }) => {
|
|
539
|
+
if (collection !== 'pages' || slug === undefined) return null
|
|
540
|
+
// Match the site's public page routing, including its homepage convention.
|
|
541
|
+
return slug === 'home' ? '/' : `/${slug}`
|
|
542
|
+
},
|
|
498
543
|
descriptions: {
|
|
499
544
|
block: 'Try different content on a landing page.',
|
|
500
545
|
document: 'Try different versions of a donation ask.',
|
|
@@ -503,11 +548,21 @@ admin: {
|
|
|
503
548
|
}
|
|
504
549
|
```
|
|
505
550
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
551
|
+
`PreviewPlacement` is exported from the package root and `/rsc`. Its `slug` is
|
|
552
|
+
the published main row's string slug, including for a placement found in a newer
|
|
553
|
+
draft with a different slug. It is absent for unpublished documents. `published`
|
|
554
|
+
describes the placement's version; the callback receives no other document fields.
|
|
555
|
+
An async callback can also look up the public path by `collection` and `id` using
|
|
556
|
+
the host's own data access. The old `({ collection, id }) => string` form still works.
|
|
557
|
+
|
|
558
|
+
Preflight resolves the URL callbacks on the server for block placements and sends
|
|
559
|
+
only URL strings to the client. A null/undefined result, a non-string, an exception,
|
|
560
|
+
a rejected promise or a timeout leaves preview hidden for that placement. When an
|
|
561
|
+
editor URL exists, the admin offers "Edit on page"; when neither URL exists, it
|
|
562
|
+
omits the row. Timed-out host work cannot be cancelled and keeps its concurrency
|
|
563
|
+
slot until it settles; if all 8 slots remain occupied, further preview lookups
|
|
564
|
+
are skipped for that preflight. Kind descriptions are available to the server views through
|
|
565
|
+
`config.custom.xp`.
|
|
511
566
|
|
|
512
567
|
A flag point can also declare `description?: string` to explain the setting below its label.
|
|
513
568
|
|
|
@@ -544,7 +599,10 @@ separately. Only manual, viewport and pageview exposure policies are accepted.
|
|
|
544
599
|
Conversions require a client-writable source, never revenue; client values are
|
|
545
600
|
stored as `1`, attributes are allow-listed, and the host's `events.rateLimit`
|
|
546
601
|
adapter runs when supplied. Accepted and dropped events both return `202 {}`;
|
|
547
|
-
malformed requests can return errors.
|
|
602
|
+
malformed requests can return errors. `click:<key>` conversions are also accepted
|
|
603
|
+
for defined click-goal keys matching `[a-z0-9][a-z0-9_-]{0,63}`; undefined or
|
|
604
|
+
invalid keys are dropped with `202`. Clicks carry no attributes, and defined
|
|
605
|
+
keys may be cached for up to 30 seconds.
|
|
548
606
|
|
|
549
607
|
**The real bound on slow or method-override bodies is the host firewall/platform
|
|
550
608
|
timeout.** A stream under the byte limit can remain open, and Payload can read
|
|
@@ -567,7 +625,8 @@ dashboard also includes **Tests** and **Goals** cards above its widgets. Goals
|
|
|
567
625
|
appears only for users who can manage tests. Both additions follow the host's
|
|
568
626
|
admin route and keep host-provided navigation and dashboard entries first.
|
|
569
627
|
After upgrading, run `payload generate:importmap` to register the dashboard card
|
|
570
|
-
component.
|
|
628
|
+
component and the server nav wrapper.
|
|
629
|
+
A host nav group also labelled "Tests" shares Payload's NavGroup DOM id and open-state preference with the plugin's group, so pick another label.
|
|
571
630
|
|
|
572
631
|
### Client assignments and exposure
|
|
573
632
|
|
|
@@ -620,19 +679,70 @@ configured to allow client writes; record revenue on the server.
|
|
|
620
679
|
|
|
621
680
|
### Puck render and editor configs
|
|
622
681
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
682
|
+
Works with: payload-puck ≥0.10.3 (experiments ≥0.4.0).
|
|
683
|
+
|
|
684
|
+
For payload-puck hosts, add one integration object on each side. In the render
|
|
685
|
+
config, pass `integrations` to `extendConfig`:
|
|
627
686
|
|
|
628
687
|
```ts
|
|
629
|
-
|
|
630
|
-
|
|
688
|
+
import { extendConfig } from '@delmaredigital/payload-puck/config'
|
|
689
|
+
import { experimentsPuck } from '@delmaredigital/payload-experiments/puck'
|
|
690
|
+
import { baseConfig } from './baseConfig'
|
|
691
|
+
|
|
692
|
+
export const renderConfig = extendConfig({
|
|
693
|
+
base: baseConfig,
|
|
694
|
+
integrations: [experimentsPuck()],
|
|
695
|
+
})
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
Call `experimentsPuckEditor()` once at module scope in a `'use client'` module,
|
|
699
|
+
then pass that array to `PuckConfigProvider`. A new object per render rebuilds
|
|
700
|
+
Puck's config and plugin list.
|
|
701
|
+
|
|
702
|
+
```tsx
|
|
703
|
+
'use client'
|
|
631
704
|
|
|
632
|
-
|
|
633
|
-
|
|
705
|
+
import { PuckConfigProvider } from '@delmaredigital/payload-puck/client'
|
|
706
|
+
import { experimentsPuckEditor } from '@delmaredigital/payload-experiments/puck/editor'
|
|
707
|
+
import { editorConfig } from './editorConfig'
|
|
708
|
+
|
|
709
|
+
const integrations = [experimentsPuckEditor()]
|
|
710
|
+
|
|
711
|
+
export function PuckProvider({ children }: { children: React.ReactNode }) {
|
|
712
|
+
return <PuckConfigProvider config={editorConfig} integrations={integrations}>
|
|
713
|
+
{children}
|
|
714
|
+
</PuckConfigProvider>
|
|
715
|
+
}
|
|
634
716
|
```
|
|
635
717
|
|
|
718
|
+
Each object includes the test block and its **A/B tests** sidebar category. The
|
|
719
|
+
editor object also includes the outline plugin: it hides empty version slots
|
|
720
|
+
that the block's test doesn't use, while content in unused slots stays visible
|
|
721
|
+
with a warning. Both helpers accept `{ routes }`; the editor also accepts
|
|
722
|
+
`accent`. Use the host's Payload routes and the plugin's `theme.accent`.
|
|
723
|
+
|
|
724
|
+
#### Wiring Puck by hand
|
|
725
|
+
|
|
726
|
+
The separate pieces stay exported:
|
|
727
|
+
|
|
728
|
+
- `/puck`: `ExperimentSlot`, `createExperimentSlot({ routes })`,
|
|
729
|
+
`EXPERIMENT_SLOT_TYPE` and `createExperimentSlotField({ routes })`.
|
|
730
|
+
- `/puck/editor`: `ExperimentSlotEditor`,
|
|
731
|
+
`createExperimentSlotEditor({ routes, accent })` and `experimentOutlinePlugin()`.
|
|
732
|
+
|
|
733
|
+
Register the render and editor blocks under the same `EXPERIMENT_SLOT_TYPE` key (import it from `/puck`),
|
|
734
|
+
add a sidebar category listing it, and add `experimentOutlinePlugin()` to Puck's
|
|
735
|
+
`plugins`. It composes with the host's outline override.
|
|
736
|
+
|
|
737
|
+
Routes default to Payload's `/admin` and `/api` when omitted. The existing
|
|
738
|
+
`ExperimentSlot` and `ExperimentSlotEditor` exports remain available with these
|
|
739
|
+
defaults. Hosts on non-default routes switch to the factories. The standalone
|
|
740
|
+
`createExperimentSlotField({ routes })` also accepts the host's routes.
|
|
741
|
+
|
|
742
|
+
Pass the same accent as the plugin's `theme.accent`: the editor cannot read
|
|
743
|
+
Payload config. Both use the same validator and report the same error for an
|
|
744
|
+
invalid colour. Without an accent the editor uses the neutral palette.
|
|
745
|
+
|
|
636
746
|
Load the editor stylesheet inside the Puck iframe. With Tailwind v4, add the
|
|
637
747
|
package import **after** Tailwind in the CSS that builds your editor iframe
|
|
638
748
|
stylesheet (the stylesheet listed in payload-puck's `editorStylesheets`):
|
|
@@ -648,6 +758,15 @@ The editor CSS includes its own light and dark palette, selected by
|
|
|
648
758
|
`data-theme="dark"` or the `dark` class on the iframe's `<html>`. The client component's CSS import alone
|
|
649
759
|
does not reach an iframe configured with `syncHostStyles: false`.
|
|
650
760
|
|
|
761
|
+
If Puck runs outside Payload's admin, the page needs Payload's admin styles (the plugin's tokens map onto them) and the plugin's admin styles, plus `data-theme="light"` or `data-theme="dark"` on `<html>`. Inside the admin all of this is already in place:
|
|
762
|
+
|
|
763
|
+
```css
|
|
764
|
+
@import "@payloadcms/next/css";
|
|
765
|
+
@import "@payloadcms/ui/styles.css";
|
|
766
|
+
@import "@delmaredigital/payload-experiments/admin/theme.css";
|
|
767
|
+
@import "@delmaredigital/payload-experiments/admin/ui.css";
|
|
768
|
+
```
|
|
769
|
+
|
|
651
770
|
Resolve the Puck data on the server with `resolveExperimentData` before
|
|
652
771
|
rendering it. The render component shows only the selected group's slot. The
|
|
653
772
|
editor lets you select a block experiment, edit each group's content, copy the
|
|
@@ -757,3 +876,35 @@ The WCAG 2.x gate requires at least 4.5:1 **foreground text on the accent fill**
|
|
|
757
876
|
in each theme (the accent is used as a fill, not as page text). A failing
|
|
758
877
|
colour is refused with its ratio. Status chips retain Payload semantic colours.
|
|
759
878
|
`deriveAccent` is also exported from `/puck/editor` for editor integrations.
|
|
879
|
+
|
|
880
|
+
## Click goals
|
|
881
|
+
|
|
882
|
+
Click goals use a goal's short name as their key (`click:<short-name>`). Add
|
|
883
|
+
`xpGoalField()` from the root entry to a host block or array row. Its manager
|
|
884
|
+
picker includes inline creation with "+ New click goal"; run
|
|
885
|
+
`payload generate:importmap` to register the picker. For Puck's `xpGoal` prop,
|
|
886
|
+
use `xpGoalPuckEditorField()` from `/puck/editor` in your editor config and
|
|
887
|
+
`xpGoalPuckField()` from `/puck` in your render config. Run the
|
|
888
|
+
host's `payload migrate:create`: each table using the Payload field (and its
|
|
889
|
+
version table) gains a nullable text column. Render the value on the actual
|
|
890
|
+
interactive element as `data-xp-goal={xpGoal}`; code can also set
|
|
891
|
+
`data-xp-goal="join"` directly. Define a click goal with that short name first.
|
|
892
|
+
For managers, an undefined key blocks the save through the Payload field's
|
|
893
|
+
server validation, refreshing a cached miss immediately.
|
|
894
|
+
Draft saves are also refused when the collection validates drafts
|
|
895
|
+
(`versions.drafts.validate`). Editors without goal-management access see a
|
|
896
|
+
plain text input. Goals in
|
|
897
|
+
related documents and keys placed only in code are invisible to the content
|
|
898
|
+
fairness check.
|
|
899
|
+
|
|
900
|
+
`ExperimentsProvider` records native clicks when its server assignments arm
|
|
901
|
+
click goals. Nested providers share one document listener; the deepest armed
|
|
902
|
+
provider supplies the route and pageview id. Enter on links/buttons and Space
|
|
903
|
+
on buttons count through the browser's native click. A focusable `div` needs
|
|
904
|
+
`role="button"` and the host's own keyboard handler. Beacons use `sendBeacon`
|
|
905
|
+
(with a keepalive fallback) so navigation/unload does not discard the click.
|
|
906
|
+
Click and exposure beacons share `events.rateLimit`; size the adapter for both.
|
|
907
|
+
New click goals are accepted within 30 seconds. A click goal may be primary;
|
|
908
|
+
primary and guardrail goals must appear in every tested version or outside all
|
|
909
|
+
of them. Secondary asymmetry warns, and results health checks published rows
|
|
910
|
+
again on every run.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { SelectInput, useField } from '@payloadcms/ui';
|
|
4
|
-
import { metricSourceHint, metricSourceOptions } from './metricSource.model.js';
|
|
4
|
+
import { metricSourceHint, metricSourceOptions, sourceFor } from './metricSource.model.js';
|
|
5
5
|
import { METRICS_COPY } from './copy/metrics.js';
|
|
6
6
|
export function MetricSourceSelect({ path, field, readOnly }) {
|
|
7
7
|
const { value, setValue } = useField({
|
|
8
8
|
path
|
|
9
9
|
});
|
|
10
10
|
const sources = field.admin?.custom?.sources ?? [];
|
|
11
|
-
const hint = metricSourceHint(
|
|
11
|
+
const hint = metricSourceHint(sourceFor(value, sources));
|
|
12
12
|
const locked = Boolean(readOnly || field.admin?.readOnly);
|
|
13
13
|
return /*#__PURE__*/ _jsxs("div", {
|
|
14
14
|
children: [
|
|
@@ -19,7 +19,7 @@ export function MetricSourceSelect({ path, field, readOnly }) {
|
|
|
19
19
|
description: field.admin?.description,
|
|
20
20
|
required: field.required,
|
|
21
21
|
options: metricSourceOptions(sources),
|
|
22
|
-
value: value ?? '',
|
|
22
|
+
value: sourceFor(value, sources)?.id ?? value ?? '',
|
|
23
23
|
readOnly: locked,
|
|
24
24
|
onChange: (option)=>{
|
|
25
25
|
if (!locked) setValue(option && !Array.isArray(option) ? option.value : null);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useConfig, useDocumentInfo } from '@payloadcms/ui';
|
|
4
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { formatAdminURL } from 'payload/shared';
|
|
6
|
+
import { createApi } from './api.js';
|
|
7
|
+
import { pageStatusCopy } from './placementStatus.model.js';
|
|
8
|
+
/** Manage-only endpoint: access-filtered editors see no status or error text. */ export function PlacementStatus(_props) {
|
|
9
|
+
const { id, collectionSlug } = useDocumentInfo();
|
|
10
|
+
const { config } = useConfig();
|
|
11
|
+
const api = useMemo(()=>createApi(config.routes.api), [
|
|
12
|
+
config.routes.api
|
|
13
|
+
]);
|
|
14
|
+
const [tests, setTests] = useState([]);
|
|
15
|
+
useEffect(()=>{
|
|
16
|
+
let active = true;
|
|
17
|
+
setTests([]);
|
|
18
|
+
if (id != null && collectionSlug) void api.placementStatus({
|
|
19
|
+
docs: [
|
|
20
|
+
{
|
|
21
|
+
collection: collectionSlug,
|
|
22
|
+
id
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}).then((result)=>{
|
|
26
|
+
if (active && result.ok) setTests(result.data?.docs[`${collectionSlug}:${id}`]?.tests ?? []);
|
|
27
|
+
});
|
|
28
|
+
return ()=>{
|
|
29
|
+
active = false;
|
|
30
|
+
};
|
|
31
|
+
}, [
|
|
32
|
+
api,
|
|
33
|
+
collectionSlug,
|
|
34
|
+
id
|
|
35
|
+
]);
|
|
36
|
+
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
37
|
+
children: tests.map((test)=>{
|
|
38
|
+
const copy = pageStatusCopy(test);
|
|
39
|
+
return copy ? /*#__PURE__*/ _jsxs("p", {
|
|
40
|
+
children: [
|
|
41
|
+
copy,
|
|
42
|
+
' ',
|
|
43
|
+
/*#__PURE__*/ _jsx("a", {
|
|
44
|
+
href: formatAdminURL({
|
|
45
|
+
adminRoute: config.routes.admin,
|
|
46
|
+
path: `/experiments/by-slug/${encodeURIComponent(test.slug)}`
|
|
47
|
+
}),
|
|
48
|
+
children: "Back to the test"
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
}, test.slug) : null;
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DocId } from '../types/contracts.js';
|
|
2
2
|
import type { PreflightResult } from '../types/endpoints.js';
|
|
3
|
-
export declare function PreviewLinks({ experimentId, placements, arms, flagPoint }: {
|
|
3
|
+
export declare function PreviewLinks({ experimentId, placements, arms, flagPoint, apiRoute }: {
|
|
4
|
+
apiRoute: string;
|
|
4
5
|
flagPoint?: string;
|
|
5
6
|
experimentId: DocId;
|
|
6
7
|
placements: PreflightResult['placements'];
|
|
@@ -5,10 +5,11 @@ import { Button } from '../ui/Button.js';
|
|
|
5
5
|
import { armLabel } from './labels.js';
|
|
6
6
|
import { useApi } from './useApi.js';
|
|
7
7
|
import { REVIEW_COPY as C } from './copy/review.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import { TEST_PAGE } from './copy/testPage.js';
|
|
9
|
+
import { API_MESSAGES, refusalMessage } from './copy/common.js';
|
|
10
|
+
import { actionablePreviewPages } from './previewLinks.model.js';
|
|
11
|
+
export function PreviewLinks({ experimentId, placements, arms, flagPoint, apiRoute }) {
|
|
12
|
+
const api = useApi(apiRoute);
|
|
12
13
|
const inFlight = useRef(false);
|
|
13
14
|
const [busy, setBusy] = useState(false);
|
|
14
15
|
const [error, setError] = useState(null);
|
|
@@ -17,20 +18,37 @@ export function PreviewLinks({ experimentId, placements, arms, flagPoint }) {
|
|
|
17
18
|
inFlight.current = true;
|
|
18
19
|
setBusy(true);
|
|
19
20
|
setError(null);
|
|
21
|
+
let win = null;
|
|
20
22
|
try {
|
|
23
|
+
win = window.open('', '_blank');
|
|
24
|
+
if (!win) {
|
|
25
|
+
setError(C.previewBlocked);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Clearing opener immediately gives the same opener protection as noopener: the
|
|
29
|
+
// previewed page cannot reach window.opener. The admin URL may be sent as
|
|
30
|
+
// referrer to the host's own site (resolved against location.origin); accepted.
|
|
31
|
+
try {
|
|
32
|
+
win.opener = null;
|
|
33
|
+
} catch {}
|
|
21
34
|
const result = await api.previewToken(experimentId, arm);
|
|
35
|
+
// The person closed the blank tab while the token loaded: nothing to show or report.
|
|
36
|
+
if (win.closed) return;
|
|
22
37
|
if (!result.ok) {
|
|
23
|
-
|
|
38
|
+
win.close();
|
|
39
|
+
setError(result.status === 0 ? API_MESSAGES.network : refusalMessage(result.code, result.status));
|
|
24
40
|
return;
|
|
25
41
|
}
|
|
26
|
-
if (
|
|
42
|
+
if (typeof result.data?.token !== 'string' || !result.data.token) {
|
|
43
|
+
win.close();
|
|
27
44
|
setError(API_MESSAGES.unreadable);
|
|
28
45
|
return;
|
|
29
46
|
}
|
|
30
47
|
const target = new URL(url, window.location.origin);
|
|
31
48
|
target.searchParams.set('xp_preview', result.data.token);
|
|
32
|
-
|
|
49
|
+
win.location.replace(target.href);
|
|
33
50
|
} catch {
|
|
51
|
+
win?.close();
|
|
34
52
|
setError(API_MESSAGES.failed);
|
|
35
53
|
} finally{
|
|
36
54
|
inFlight.current = false;
|
|
@@ -40,7 +58,7 @@ export function PreviewLinks({ experimentId, placements, arms, flagPoint }) {
|
|
|
40
58
|
return /*#__PURE__*/ _jsxs("div", {
|
|
41
59
|
className: "xp-preview-links",
|
|
42
60
|
children: [
|
|
43
|
-
|
|
61
|
+
actionablePreviewPages(placements, flagPoint).map((placement)=>/*#__PURE__*/ _jsxs("div", {
|
|
44
62
|
className: "xp-preview-row",
|
|
45
63
|
children: [
|
|
46
64
|
/*#__PURE__*/ _jsxs("p", {
|
|
@@ -49,26 +67,29 @@ export function PreviewLinks({ experimentId, placements, arms, flagPoint }) {
|
|
|
49
67
|
placement.slotCount > 1 ? ` ${C.placedTimes(placement.slotCount)}` : null
|
|
50
68
|
]
|
|
51
69
|
}),
|
|
52
|
-
/*#__PURE__*/
|
|
70
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
53
71
|
className: "xp-ui-button-group",
|
|
54
|
-
children:
|
|
55
|
-
|
|
72
|
+
children: [
|
|
73
|
+
placement.url ? arms.filter((arm)=>arm.kind !== 'holdout' && arm.key !== 'holdout').map((arm)=>/*#__PURE__*/ _jsxs(Button, {
|
|
74
|
+
variant: "secondary",
|
|
75
|
+
size: "sm",
|
|
76
|
+
disabled: busy,
|
|
77
|
+
onClick: ()=>{
|
|
78
|
+
void preview(placement.url, arm.key);
|
|
79
|
+
},
|
|
80
|
+
children: [
|
|
81
|
+
C.preview,
|
|
82
|
+
" ",
|
|
83
|
+
arm.label?.trim() || armLabel(arm.key)
|
|
84
|
+
]
|
|
85
|
+
}, arm.key)) : null,
|
|
86
|
+
placement.editorUrl ? /*#__PURE__*/ _jsx(Button, {
|
|
87
|
+
variant: "link",
|
|
56
88
|
size: "sm",
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
children: [
|
|
62
|
-
C.preview,
|
|
63
|
-
" ",
|
|
64
|
-
arm.label?.trim() || armLabel(arm.key)
|
|
65
|
-
]
|
|
66
|
-
}, arm.key)) : /*#__PURE__*/ _jsx(Button, {
|
|
67
|
-
variant: "secondary",
|
|
68
|
-
size: "sm",
|
|
69
|
-
disabled: true,
|
|
70
|
-
children: C.noPreview
|
|
71
|
-
})
|
|
89
|
+
href: placement.editorUrl,
|
|
90
|
+
children: TEST_PAGE.editOnPage
|
|
91
|
+
}) : null
|
|
92
|
+
]
|
|
72
93
|
})
|
|
73
94
|
]
|
|
74
95
|
}, placement.key)),
|
|
@@ -5,7 +5,7 @@ import { Link, NavGroup, useAuth, useConfig } from '@payloadcms/ui';
|
|
|
5
5
|
import { formatAdminURL } from 'payload/shared';
|
|
6
6
|
import { usePathname } from 'next/navigation.js';
|
|
7
7
|
import { SURFACE_COPY } from './copy/surface.js';
|
|
8
|
-
function TestsNav() {
|
|
8
|
+
function TestsNav({ isOpen = true }) {
|
|
9
9
|
const { config } = useConfig();
|
|
10
10
|
const { permissions } = useAuth();
|
|
11
11
|
const pathname = usePathname();
|
|
@@ -38,9 +38,9 @@ function TestsNav() {
|
|
|
38
38
|
active: goalsActive
|
|
39
39
|
}
|
|
40
40
|
];
|
|
41
|
-
// getPreference is async and can fetch; let NavGroup default to open.
|
|
42
41
|
return /*#__PURE__*/ _jsx(NavGroup, {
|
|
43
42
|
label: SURFACE_COPY.title,
|
|
43
|
+
isOpen: isOpen,
|
|
44
44
|
children: entries.map((entry)=>{
|
|
45
45
|
const props = {
|
|
46
46
|
className: 'nav__link',
|
|
@@ -8,7 +8,8 @@ export declare function DialogFrame({ title, onClose, children, initialFocus, di
|
|
|
8
8
|
onClose: () => void;
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
}): import("react").JSX.Element;
|
|
11
|
-
export declare function ConfirmDialog({ id, action, winnerLabel, onClose, canManage, isDecided }: {
|
|
11
|
+
export declare function ConfirmDialog({ id, action, winnerLabel, onClose, canManage, isDecided, apiRoute }: {
|
|
12
|
+
apiRoute: string;
|
|
12
13
|
canManage?: boolean;
|
|
13
14
|
isDecided?: boolean;
|
|
14
15
|
id: DocId;
|
|
@@ -20,8 +20,8 @@ export function DialogFrame({ title, onClose, children, initialFocus, dismissibl
|
|
|
20
20
|
children: children
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
export function ConfirmDialog({ id, action, winnerLabel, onClose, canManage = false, isDecided = false }) {
|
|
24
|
-
const api = useApi();
|
|
23
|
+
export function ConfirmDialog({ id, action, winnerLabel, onClose, canManage = false, isDecided = false, apiRoute }) {
|
|
24
|
+
const api = useApi(apiRoute);
|
|
25
25
|
const [typed, setTyped] = useState('');
|
|
26
26
|
const [busy, setBusy] = useState(false);
|
|
27
27
|
const [reportBusy, setReportBusy] = useState(false);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { DocId } from '../../types/contracts.js';
|
|
2
2
|
import type { PreflightResult } from '../../types/endpoints.js';
|
|
3
3
|
import type { ActionGroup } from './actions.model.js';
|
|
4
|
-
export declare function DecideDialog({ id, arms, checks, onClose }: {
|
|
4
|
+
export declare function DecideDialog({ id, arms, checks, onClose, apiRoute }: {
|
|
5
|
+
apiRoute: string;
|
|
5
6
|
id: DocId;
|
|
6
7
|
arms: readonly ActionGroup[];
|
|
7
8
|
checks?: PreflightResult['checks'];
|
|
@@ -10,8 +10,8 @@ import { RESULTS_COPY } from '../copy/results.js';
|
|
|
10
10
|
import { ACTIONS_COPY as C, decideCopy } from '../copy/actions.js';
|
|
11
11
|
import { decideState } from './decide.model.js';
|
|
12
12
|
import { DialogFrame } from './ConfirmDialog.js';
|
|
13
|
-
export function DecideDialog({ id, arms, checks = [], onClose }) {
|
|
14
|
-
const api = useApi();
|
|
13
|
+
export function DecideDialog({ id, arms, checks = [], onClose, apiRoute }) {
|
|
14
|
+
const api = useApi(apiRoute);
|
|
15
15
|
const [snapshot, setSnapshot] = useState(null);
|
|
16
16
|
const [chosenArm, setChosenArm] = useState(null);
|
|
17
17
|
const [note, setNote] = useState('');
|
package/dist/admin/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DocId } from '../types/contracts.js';
|
|
2
|
-
import type { RestoreContentEntryBody, RestoreContentEntryResult, DismissContentEntryBody, DismissContentEntryResult, AddMetricBody, AllocateBody, ArchiveBody, DecideBody, MakeContentResult, PreflightResult, PreviewTokenResult, RefusalCode, ResultsResponse, SummaryResponse, TransitionSuccess } from '../types/endpoints.js';
|
|
2
|
+
import type { WrapBlockBody, PagesResult, PlacementStatusBody, PlacementStatusResult, RestoreContentEntryBody, RestoreContentEntryResult, DismissContentEntryBody, DismissContentEntryResult, AddMetricBody, AllocateBody, ArchiveBody, DecideBody, MakeContentResult, AddMetricResult, ClickKeysResult, PreflightResult, PreviewTokenResult, RefusalCode, ResultsResponse, SummaryResponse, TransitionSuccess } from '../types/endpoints.js';
|
|
3
3
|
import { refusalMessage } from './copy/common.js';
|
|
4
4
|
export type ApiResult<T> = {
|
|
5
5
|
ok: true;
|
|
@@ -13,9 +13,17 @@ export type ApiResult<T> = {
|
|
|
13
13
|
};
|
|
14
14
|
type RequestOptions = NonNullable<Parameters<typeof refusalMessage>[3]>;
|
|
15
15
|
export declare function createApi(apiBase: string): {
|
|
16
|
+
wrapBlock: (body: WrapBlockBody) => Promise<ApiResult<Record<string, unknown>>>;
|
|
17
|
+
pages: (query: {
|
|
18
|
+
collection: string;
|
|
19
|
+
q?: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
page?: number;
|
|
22
|
+
}, signal?: AbortSignal) => Promise<ApiResult<PagesResult>>;
|
|
23
|
+
placementStatus: (body: PlacementStatusBody) => Promise<ApiResult<PlacementStatusResult>>;
|
|
16
24
|
start: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
|
|
17
25
|
allocate: (id: DocId, body: AllocateBody, options?: RequestOptions) => Promise<ApiResult<TransitionSuccess>>;
|
|
18
|
-
addMetric: (id: DocId, body: AddMetricBody, options?: RequestOptions) => Promise<ApiResult<
|
|
26
|
+
addMetric: (id: DocId, body: AddMetricBody, options?: RequestOptions) => Promise<ApiResult<AddMetricResult>>;
|
|
19
27
|
stop: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
|
|
20
28
|
decide: (id: DocId, body: DecideBody) => Promise<ApiResult<TransitionSuccess>>;
|
|
21
29
|
releaseHoldout: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
|
|
@@ -24,6 +32,7 @@ export declare function createApi(apiBase: string): {
|
|
|
24
32
|
dismissContentEntry: (id: DocId, body: DismissContentEntryBody) => Promise<ApiResult<DismissContentEntryResult>>;
|
|
25
33
|
restoreContentEntry: (id: DocId, body: RestoreContentEntryBody) => Promise<ApiResult<RestoreContentEntryResult>>;
|
|
26
34
|
archive: (id: DocId, body: ArchiveBody) => Promise<ApiResult<TransitionSuccess>>;
|
|
35
|
+
clickKeys: (id: DocId) => Promise<ApiResult<ClickKeysResult>>;
|
|
27
36
|
preflight: (id: DocId) => Promise<ApiResult<PreflightResult>>;
|
|
28
37
|
previewToken: (id: DocId, arm: string) => Promise<ApiResult<PreviewTokenResult>>;
|
|
29
38
|
results: (id: DocId, options?: {
|