@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,4 +1,9 @@
|
|
|
1
|
+
import { goalContent, OUTSIDE_GOAL } from '../goals/fairness.js';
|
|
2
|
+
import { placementInteractions, placementPaths, uniquePlacementDocs } from './placementInteractions.js';
|
|
1
3
|
import { Forbidden, NotFound } from 'payload';
|
|
4
|
+
import { normalisePath } from '../runtime/normalisePath.js';
|
|
5
|
+
import { resolvePageCollection } from './pageCollection.js';
|
|
6
|
+
import { identicalPageVersions } from './pageVersions.js';
|
|
2
7
|
import { safeError } from '../log.js';
|
|
3
8
|
import { powerMetricWeeks } from '@delmaredigital/experiments-stats';
|
|
4
9
|
import { checkStart, relationshipId } from './checkStart.js';
|
|
@@ -35,13 +40,18 @@ const failureDetail = (cause)=>{
|
|
|
35
40
|
return routedPageTests(await req.transactionID != null ? txExecutor(req) : pooledExecutor(req.payload));
|
|
36
41
|
}
|
|
37
42
|
/** Page targets no registered collection reports as published. A lookup that throws counts as
|
|
38
|
-
* unpublished (logged). Start calls it again under its page-path locks on a detached request. */ export async function unpublishedPageTargets(req, options, experiment) {
|
|
43
|
+
* unpublished (logged) unless propagateErrors is requested by decide. Start calls it again under its page-path locks on a detached request. */ export async function unpublishedPageTargets(req, options, experiment, propagateErrors = false) {
|
|
44
|
+
const resolved = await resolvePageCollection(req, options, experiment, propagateErrors);
|
|
45
|
+
const collections = resolved ? [
|
|
46
|
+
resolved
|
|
47
|
+
] : [];
|
|
39
48
|
const targets = await Promise.all(experiment.arms.filter((arm)=>arm.kind !== 'holdout' && arm.pageTarget?.trim()).map(async (arm)=>{
|
|
40
|
-
for (const collection of
|
|
49
|
+
for (const collection of collections){
|
|
41
50
|
try {
|
|
42
51
|
const page = await collection.findByPath(arm.pageTarget, req);
|
|
43
52
|
if (page?.published === true) return null;
|
|
44
53
|
} catch (cause) {
|
|
54
|
+
if (propagateErrors) throw cause;
|
|
45
55
|
req.payload.logger.error({
|
|
46
56
|
err: safeError(cause),
|
|
47
57
|
experimentId: experiment.id,
|
|
@@ -114,13 +124,19 @@ const metricIds = (experiment)=>[
|
|
|
114
124
|
pageTestsInUse ??= loadPageTestsInUse(req);
|
|
115
125
|
input.pageTestsInUse = await pageTestsInUse;
|
|
116
126
|
input.pageTargetsUnchecked = options.pages === undefined;
|
|
117
|
-
|
|
127
|
+
input.unknownPageCollection = Boolean(experiment.pageCollection && !options.pages?.collections.some((entry)=>entry.slug === experiment.pageCollection));
|
|
128
|
+
if (options.pages) {
|
|
129
|
+
input.unpublishedPageTargets = await unpublishedPageTargets(req, options, experiment);
|
|
130
|
+
if (options.pages.collections.some((entry)=>entry.contentOf)) input.identicalVersions = await identicalPageVersions(req, await resolvePageCollection(req, options, experiment), experiment.arms);
|
|
131
|
+
}
|
|
118
132
|
}
|
|
119
133
|
if (experiment.kind === 'block') {
|
|
120
134
|
try {
|
|
121
135
|
blocks ??= scanBlockPlacements(req, {
|
|
122
136
|
slugs: blockSlugs,
|
|
123
|
-
|
|
137
|
+
compareVersions: true,
|
|
138
|
+
puckCollections: options.puck?.collections ?? [],
|
|
139
|
+
blocksCollections: options.blocks?.collections ?? []
|
|
124
140
|
});
|
|
125
141
|
input.placements = (await blocks).get(experiment.slug) ?? [];
|
|
126
142
|
} catch (cause) {
|
|
@@ -250,6 +266,13 @@ const metricIds = (experiment)=>[
|
|
|
250
266
|
})());
|
|
251
267
|
input.layer = await layers.get(key);
|
|
252
268
|
}
|
|
269
|
+
if ((experiment.kind === 'page' || experiment.kind === 'block') && options.pages?.collections.length) {
|
|
270
|
+
const paths = experiment.kind === 'page' ? experiment.arms.flatMap((a)=>typeof a.pageTarget === 'string' ? [
|
|
271
|
+
a.pageTarget
|
|
272
|
+
] : []) : await placementPaths(req, options, uniquePlacementDocs(input.placements ?? []));
|
|
273
|
+
input.interactions = await placementInteractions(req, await req.transactionID != null ? txExecutor(req) : pooledExecutor(req.payload), options, experiment, paths.map(normalisePath));
|
|
274
|
+
}
|
|
275
|
+
if (input.metrics.some((metric)=>metric.source.startsWith('click:'))) input.goalContent = await goalContent(req, options, experiment, input.placements ? uniquePlacementDocs(input.placements.filter((placement)=>placement.published)) : undefined);
|
|
253
276
|
inputs.set(String(experiment.id), input);
|
|
254
277
|
} catch (cause) {
|
|
255
278
|
if (!tolerateErrors) throw cause;
|
|
@@ -279,7 +302,9 @@ async function placementUrls(options, placement, pendingPreviews, onFailure, pre
|
|
|
279
302
|
const url = options.admin?.editorUrl?.({
|
|
280
303
|
collection,
|
|
281
304
|
id,
|
|
282
|
-
draft: !published
|
|
305
|
+
draft: !published,
|
|
306
|
+
editor: placement.editor ?? 'puck',
|
|
307
|
+
doc: placement.doc
|
|
283
308
|
});
|
|
284
309
|
if (typeof url === 'string') result.editorUrl = url;
|
|
285
310
|
} catch {}
|
|
@@ -476,9 +501,116 @@ export async function preflightData(req, { experimentId, options }, { now = new
|
|
|
476
501
|
detail: placement.detail
|
|
477
502
|
} : {}
|
|
478
503
|
});
|
|
504
|
+
const pagePlacements = [];
|
|
505
|
+
if (e.kind === 'page') {
|
|
506
|
+
const collection = await resolvePageCollection(req, options, e);
|
|
507
|
+
for (const arm of e.arms.filter((arm)=>arm.kind !== 'holdout')){
|
|
508
|
+
const path = arm.pageTarget ?? '';
|
|
509
|
+
let page = null;
|
|
510
|
+
try {
|
|
511
|
+
page = path && collection ? await collection.findByPath(path, req) : null;
|
|
512
|
+
} catch (cause) {
|
|
513
|
+
onPreviewFailure(cause, {
|
|
514
|
+
collection: collection.slug,
|
|
515
|
+
id: path,
|
|
516
|
+
title: path,
|
|
517
|
+
published: false,
|
|
518
|
+
slots: {
|
|
519
|
+
control: 0,
|
|
520
|
+
variantA: 0,
|
|
521
|
+
variantB: 0,
|
|
522
|
+
variantC: 0
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (!page || !collection) {
|
|
527
|
+
pagePlacements.push({
|
|
528
|
+
arm: arm.key,
|
|
529
|
+
path,
|
|
530
|
+
published: false,
|
|
531
|
+
id: null,
|
|
532
|
+
...collection ? {
|
|
533
|
+
collection: collection.slug
|
|
534
|
+
} : {}
|
|
535
|
+
});
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
let doc;
|
|
539
|
+
try {
|
|
540
|
+
doc = await req.payload.findByID({
|
|
541
|
+
collection: collection.slug,
|
|
542
|
+
id: page.id,
|
|
543
|
+
req,
|
|
544
|
+
overrideAccess: false,
|
|
545
|
+
depth: 0,
|
|
546
|
+
draft: !page.published
|
|
547
|
+
});
|
|
548
|
+
} catch (cause) {
|
|
549
|
+
onPreviewFailure(cause, {
|
|
550
|
+
collection: collection.slug,
|
|
551
|
+
id: page.id,
|
|
552
|
+
title: path,
|
|
553
|
+
published: false,
|
|
554
|
+
slots: {
|
|
555
|
+
control: 0,
|
|
556
|
+
variantA: 0,
|
|
557
|
+
variantB: 0,
|
|
558
|
+
variantC: 0
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
pagePlacements.push({
|
|
562
|
+
arm: arm.key,
|
|
563
|
+
path,
|
|
564
|
+
collection: collection.slug,
|
|
565
|
+
published: false,
|
|
566
|
+
id: null
|
|
567
|
+
});
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
const titleField = req.payload.config.collections?.find((entry)=>entry.slug === collection.slug)?.admin.useAsTitle ?? 'title';
|
|
571
|
+
const placement = {
|
|
572
|
+
collection: collection.slug,
|
|
573
|
+
id: page.id,
|
|
574
|
+
title: String(doc[titleField] ?? doc.id),
|
|
575
|
+
published: page.published,
|
|
576
|
+
...page.published && typeof doc.slug === 'string' ? {
|
|
577
|
+
slug: doc.slug
|
|
578
|
+
} : {},
|
|
579
|
+
slots: {
|
|
580
|
+
control: 0,
|
|
581
|
+
variantA: 0,
|
|
582
|
+
variantB: 0,
|
|
583
|
+
variantC: 0
|
|
584
|
+
},
|
|
585
|
+
editor: 'page',
|
|
586
|
+
doc
|
|
587
|
+
};
|
|
588
|
+
pagePlacements.push({
|
|
589
|
+
arm: arm.key,
|
|
590
|
+
path,
|
|
591
|
+
collection: collection.slug,
|
|
592
|
+
id: page.id,
|
|
593
|
+
title: placement.title,
|
|
594
|
+
published: placement.published,
|
|
595
|
+
...placement.slug ? {
|
|
596
|
+
slug: placement.slug
|
|
597
|
+
} : {},
|
|
598
|
+
...await placementUrls(options, placement, pendingPreviews, onPreviewFailure, previews)
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
479
602
|
return {
|
|
480
603
|
checks: checkStart(input),
|
|
604
|
+
...input.goalContent ? {
|
|
605
|
+
goalNotes: input.metrics.filter((m)=>m.source.startsWith('click:') && input.goalContent && !input.goalContent.skipped && !input.goalContent.unchecked.length && !input.goalContent.keys.some((k)=>`click:${k.key}` === m.source)).map((m)=>({
|
|
606
|
+
metricId: m.id,
|
|
607
|
+
message: OUTSIDE_GOAL
|
|
608
|
+
}))
|
|
609
|
+
} : {},
|
|
481
610
|
placements,
|
|
611
|
+
...e.kind === 'page' ? {
|
|
612
|
+
pagePlacements
|
|
613
|
+
} : {},
|
|
482
614
|
baseline,
|
|
483
615
|
trafficPerDay,
|
|
484
616
|
estimate: power
|
|
@@ -2,6 +2,7 @@ import type { DocId, MetricSource } from '../types/contracts.js';
|
|
|
2
2
|
export interface MetricDefinition extends Record<string, unknown> {
|
|
3
3
|
id: DocId;
|
|
4
4
|
source: string;
|
|
5
|
+
direction?: 'increase' | 'decrease';
|
|
5
6
|
attributionWindowHours?: number | null;
|
|
6
7
|
minSampleSize?: number | null;
|
|
7
8
|
guardrailThreshold?: number | null;
|
|
@@ -8,7 +8,7 @@ import { perUnitHistory } from '../results/history.js';
|
|
|
8
8
|
const from = new Date(start.getTime() - 90 * 86400000);
|
|
9
9
|
for (const metric of metrics){
|
|
10
10
|
const source = sources.find((source)=>source.id === metric.source);
|
|
11
|
-
const kind = metric.source.startsWith('events:') ? 'binomial' : source?.kind;
|
|
11
|
+
const kind = metric.source.startsWith('events:') || metric.source.startsWith('click:') ? 'binomial' : source?.kind;
|
|
12
12
|
const definition = {
|
|
13
13
|
...structuredClone(metric),
|
|
14
14
|
...source ? {
|
|
@@ -19,11 +19,15 @@ import { perUnitHistory } from '../results/history.js';
|
|
|
19
19
|
historyUnits: undefined,
|
|
20
20
|
capNotice: undefined
|
|
21
21
|
};
|
|
22
|
+
if (metric.source.startsWith('click:')) {
|
|
23
|
+
definition.direction = 'increase';
|
|
24
|
+
definition.capping = null;
|
|
25
|
+
}
|
|
22
26
|
const cap = metric.capping?.type ? metric.capping : kind === 'revenue' ? {
|
|
23
27
|
type: 'percentile',
|
|
24
28
|
value: 0.99
|
|
25
29
|
} : null;
|
|
26
|
-
if (kind === 'revenue' || !source && cap && !metric.source.startsWith('events:')) {
|
|
30
|
+
if (kind === 'revenue' || !source && cap && !(metric.source.startsWith('events:') || metric.source.startsWith('click:'))) {
|
|
27
31
|
definition.capping = cap;
|
|
28
32
|
if (cap?.type === 'absolute' && typeof cap.value === 'number' && Number.isFinite(cap.value) && cap.value >= 0) {
|
|
29
33
|
definition.resolvedCap = cap.value;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { goalContent } from '../goals/fairness.js';
|
|
2
|
+
import { detached } from './placementInteractions.js';
|
|
3
|
+
import { lockAndRescanPlacements, placementInteractions } from './placementInteractions.js';
|
|
1
4
|
import { lockPagePaths } from '../db/locks.js';
|
|
2
5
|
import { lockMetricsForFreeze } from '../db/metricUsage.js';
|
|
3
6
|
import { DEFAULT_SEQUENTIAL_TUNING_PARAMETER } from '../constants.js';
|
|
4
7
|
import { claimSlices } from '../db/slices.js';
|
|
5
|
-
import { pageInUseCopy, unpublishedPageCopy } from '../admin/copy/readiness.js';
|
|
8
|
+
import { asymmetricGoalsCopy, sameAddressCopy, interactingTestsCopy, pageInUseCopy, unpublishedPageCopy } from '../admin/copy/readiness.js';
|
|
9
|
+
import { GROUP_LABELS } from '../admin/copy/arms.js';
|
|
6
10
|
import { XpRefusal } from '../errors.js';
|
|
7
11
|
import { isOpValid } from '../targeting/ops.js';
|
|
8
12
|
import { newSeed } from '../runtime/seed.js';
|
|
@@ -31,7 +35,19 @@ export async function readExperiment(req, id) {
|
|
|
31
35
|
}
|
|
32
36
|
export function refuse(code, detail) {
|
|
33
37
|
const messages = {
|
|
38
|
+
'goal-in-some-versions': asymmetricGoalsCopy(detail),
|
|
39
|
+
'placements-changed': "This test's pages changed while it was starting. Try again.",
|
|
40
|
+
'page-busy': "One of this test's pages is being saved right now. Try again in a moment.",
|
|
41
|
+
'interacting-tests': (()=>{
|
|
42
|
+
const d = detail?.[0];
|
|
43
|
+
return d ? interactingTestsCopy(d.path, d.status, d.name, d.version) : 'Finish one of the tests first.';
|
|
44
|
+
})(),
|
|
45
|
+
'unknown-collection': "That page type isn't set up for tests.",
|
|
34
46
|
'page-in-use': pageInUseCopy(detail?.paths?.[0] ?? 'this page'),
|
|
47
|
+
'version-same-address': (()=>{
|
|
48
|
+
const d = detail;
|
|
49
|
+
return sameAddressCopy(GROUP_LABELS[d?.arm ?? 'variantA'], GROUP_LABELS[d?.otherArm ?? 'control'], d?.path ?? 'this address');
|
|
50
|
+
})(),
|
|
35
51
|
'page-target-unpublished': unpublishedPageCopy(typeof detail === 'string' ? detail : 'This page'),
|
|
36
52
|
'invalid-status': 'This action is not available in the current status.',
|
|
37
53
|
'stale-draft': 'The experiment changed. Review it again before starting.',
|
|
@@ -46,6 +62,9 @@ export function refuse(code, detail) {
|
|
|
46
62
|
'entry-not-dismissed': 'That page is not dismissed.'
|
|
47
63
|
};
|
|
48
64
|
const conflict = [
|
|
65
|
+
'page-busy',
|
|
66
|
+
'placements-changed',
|
|
67
|
+
'interacting-tests',
|
|
49
68
|
'invalid-status',
|
|
50
69
|
'stale-draft',
|
|
51
70
|
'page-in-use',
|
|
@@ -110,6 +129,17 @@ export async function start(req, { experimentId, options }) {
|
|
|
110
129
|
const current = await readExperiment(req, experimentId);
|
|
111
130
|
if (current.updatedAt !== updatedAt) refuse('stale-draft');
|
|
112
131
|
if (current.status !== 'draft' && current.status !== 'stopped') refuse('invalid-status');
|
|
132
|
+
let placementDocs;
|
|
133
|
+
if (current.kind === 'block') {
|
|
134
|
+
const rescan = await lockAndRescanPlacements(req, exec, options, current, input.placements ?? []);
|
|
135
|
+
placementDocs = rescan.docs;
|
|
136
|
+
const failure = checkStart({
|
|
137
|
+
...input,
|
|
138
|
+
experiment: current,
|
|
139
|
+
placements: rescan.placements
|
|
140
|
+
}).find((check)=>!check.ok);
|
|
141
|
+
if (failure && failure.code !== 'page-targets-unchecked' && failure.code !== 'make-content-unsupported') refuse(failure.code, failure.detail);
|
|
142
|
+
}
|
|
113
143
|
if (current.kind === 'page') {
|
|
114
144
|
// Independent experiment/layer locks do not serialize overlapping pages.
|
|
115
145
|
// Re-read occupancy after acquiring sorted path locks; hold through commit.
|
|
@@ -121,6 +151,8 @@ export async function start(req, { experimentId, options }) {
|
|
|
121
151
|
pageTestsInUse: await loadPageTestsInUse(req)
|
|
122
152
|
}).find((check)=>check.code === 'page-in-use' && !check.ok);
|
|
123
153
|
if (recheck) refuse('page-in-use', recheck.detail);
|
|
154
|
+
const conflicts = await placementInteractions(req, exec, options, current, pageTargetPaths(current.arms));
|
|
155
|
+
if (conflicts.length) refuse('interacting-tests', conflicts);
|
|
124
156
|
if (options.pages) {
|
|
125
157
|
// The page guard takes the same path locks, so a delete, unpublish or rename that committed
|
|
126
158
|
// while Start waited is visible now. Read it on a detached request (committed state, outside
|
|
@@ -181,6 +213,15 @@ export async function start(req, { experimentId, options }) {
|
|
|
181
213
|
}).find((check)=>!check.ok);
|
|
182
214
|
if (recheck && recheck.code !== 'page-targets-unchecked' && recheck.code !== 'make-content-unsupported') refuse(recheck.code, recheck.detail);
|
|
183
215
|
}
|
|
216
|
+
if (input.metrics.some((metric)=>metric.source.startsWith('click:'))) {
|
|
217
|
+
const content = await detached(req, ()=>goalContent(req, options, current, placementDocs));
|
|
218
|
+
const failure = checkStart({
|
|
219
|
+
...input,
|
|
220
|
+
experiment: current,
|
|
221
|
+
goalContent: content
|
|
222
|
+
}).find((check)=>check.code === 'goal-in-some-versions' && !check.ok);
|
|
223
|
+
if (failure) refuse('goal-in-some-versions', failure.detail);
|
|
224
|
+
}
|
|
184
225
|
const lockedMetrics = await lockMetricsForFreeze(exec, caps.definitions.map((metric)=>metric.id));
|
|
185
226
|
if (!lockedMetrics) {
|
|
186
227
|
throw new XpRefusal('stale-draft', 'A goal in this test was deleted. Review the goals before starting.', undefined, 409);
|
|
@@ -233,6 +274,9 @@ export async function start(req, { experimentId, options }) {
|
|
|
233
274
|
allocationChanges: [],
|
|
234
275
|
targeting,
|
|
235
276
|
metricDefinitions: definitions,
|
|
277
|
+
...placementDocs ? {
|
|
278
|
+
placementDocs
|
|
279
|
+
} : {},
|
|
236
280
|
analysis: {
|
|
237
281
|
...current.analysis,
|
|
238
282
|
method: current.analysis?.method ?? 'sequential',
|
package/dist/types/admin.d.ts
CHANGED
|
@@ -18,4 +18,4 @@ export type SerializedAttributeKeys = Record<string, {
|
|
|
18
18
|
type: 'string' | 'number' | 'boolean';
|
|
19
19
|
values?: string[];
|
|
20
20
|
}>;
|
|
21
|
-
export type EditableField = 'name' | 'hypothesis' | 'arms' | 'targeting' | 'allocation' | 'flagPoint' | 'metrics' | 'analysis' | 'exposurePolicy';
|
|
21
|
+
export type EditableField = 'name' | 'hypothesis' | 'arms' | 'targeting' | 'allocation' | 'flagPoint' | 'metrics' | 'analysis' | 'exposurePolicy' | 'pageCollection';
|
|
@@ -103,6 +103,10 @@ export type ExperimentsAccess = (args: {
|
|
|
103
103
|
}) => boolean | Promise<boolean>;
|
|
104
104
|
export interface ExperimentsPageCollection {
|
|
105
105
|
slug: string;
|
|
106
|
+
contentOf?: (doc: Record<string, unknown>) => unknown;
|
|
107
|
+
copyHint?: string;
|
|
108
|
+
copyUrl?: string;
|
|
109
|
+
copyLabel?: string;
|
|
106
110
|
pathOf: (doc: Record<string, unknown>) => string | null;
|
|
107
111
|
/** Lookup with the supplied req and overrideAccess: false. Under an anonymous req (results checks) only published pages are visible, so a hit is { published: true } or null. Under a req that can read drafts (Start), a draft-only page may be reported as { published: false }. */
|
|
108
112
|
findByPath: (path: string, req: PayloadRequest) => Promise<{
|
|
@@ -130,9 +134,12 @@ export interface ExperimentsPluginOptions<TUser extends {
|
|
|
130
134
|
collection: string;
|
|
131
135
|
id: DocId;
|
|
132
136
|
draft?: boolean;
|
|
137
|
+
editor: 'puck' | 'blocks' | 'page';
|
|
138
|
+
doc?: Record<string, unknown>;
|
|
133
139
|
}) => string;
|
|
134
140
|
previewUrl?: (placement: PreviewPlacement) => string | null | undefined | Promise<string | null | undefined>;
|
|
135
141
|
descriptions?: {
|
|
142
|
+
page?: string;
|
|
136
143
|
block?: string;
|
|
137
144
|
document?: string;
|
|
138
145
|
setting?: string;
|
|
@@ -151,6 +158,14 @@ export interface ExperimentsPluginOptions<TUser extends {
|
|
|
151
158
|
pages?: {
|
|
152
159
|
collections: ExperimentsPageCollection[];
|
|
153
160
|
};
|
|
161
|
+
blocks?: {
|
|
162
|
+
collections: Array<{
|
|
163
|
+
slug: string;
|
|
164
|
+
field: string;
|
|
165
|
+
blockSlug?: string;
|
|
166
|
+
appliesTo?: (doc: Record<string, unknown>) => boolean;
|
|
167
|
+
}>;
|
|
168
|
+
};
|
|
154
169
|
puck?: {
|
|
155
170
|
enabled: boolean;
|
|
156
171
|
collections: Array<string | {
|
|
@@ -7,7 +7,7 @@ import type { Snapshot, VerdictCode } from './snapshot.js';
|
|
|
7
7
|
export type ArmIssueCode = 'weights-total' | 'control-count' | 'control-first' | 'holdout-last' | 'holdout-count' | 'variant-count' | 'duplicate-key' | 'duplicate-slot' | 'slot-required' | 'invalid-key';
|
|
8
8
|
/** Content-report dismiss/restore refusals (CORE-44). */
|
|
9
9
|
export type ContentEntryRefusal = 'not-decided' | 'entry-not-found' | 'entry-ok' | 'entry-dismissed' | 'entry-not-dismissed';
|
|
10
|
-
export type RefusalCode = ArmIssueCode | 'not-found' | ContentEntryRefusal | 'no-planned-duration' | 'no-primary-metric' | 'no-placements' | 'empty-slot' | 'page-in-use' | 'page-no-targets' | 'page-target-unpublished' | 'page-user-unit' | 'version-page-unavailable' | 'version-page-unchecked' | 'flag-no-point' | 'flag-params-invalid' | 'flag-point-busy' | 'flag-placement-missing' | 'layer-full' | 'layer-unit-mismatch' | 'unknown-metric-source' | 'unit-source-mismatch' | 'no-reach-per-day' | 'invalid-status' | 'stale-draft' | 'stale-edit' | 'commit-unverified' | 'allocation-lower' | 'confirmation-required' | 'invalid-winner' | 'slices-taken' | 'locked-placement' | 'not-implemented' | 'forbidden' | 'unauthenticated' | 'body-too-large' | 'invalid-body' | 'too-many-ids' | 'invalid-phase' | 'invalid-ids' | 'recompute-failed' | 'analysis-failed' | 'undeclared-targeting-key' | 'invalid-targeting-rule' | 'block-path-rule' | 'duplicate-metric';
|
|
10
|
+
export type RefusalCode = ArmIssueCode | 'goal-in-some-versions' | 'placement-unchecked' | 'placement-unpublished' | 'duplicate-version-row' | 'interacting-tests' | 'placements-changed' | 'page-busy' | 'wrap-target-missing' | 'version-identical' | 'version-same-address' | 'unknown-collection' | 'not-found' | ContentEntryRefusal | 'no-planned-duration' | 'no-primary-metric' | 'no-placements' | 'empty-slot' | 'page-in-use' | 'page-no-targets' | 'page-target-unpublished' | 'page-user-unit' | 'version-page-unavailable' | 'version-page-unchecked' | 'flag-no-point' | 'flag-params-invalid' | 'flag-point-busy' | 'flag-placement-missing' | 'layer-full' | 'layer-unit-mismatch' | 'unknown-metric-source' | 'unit-source-mismatch' | 'no-reach-per-day' | 'invalid-status' | 'stale-draft' | 'stale-edit' | 'commit-unverified' | 'allocation-lower' | 'confirmation-required' | 'invalid-winner' | 'slices-taken' | 'locked-placement' | 'not-implemented' | 'forbidden' | 'unauthenticated' | 'body-too-large' | 'invalid-body' | 'too-many-ids' | 'invalid-phase' | 'invalid-ids' | 'recompute-failed' | 'analysis-failed' | 'undeclared-targeting-key' | 'invalid-targeting-rule' | 'block-path-rule' | 'duplicate-metric';
|
|
11
11
|
export type ErrorEnvelope = {
|
|
12
12
|
errors: Array<{
|
|
13
13
|
code: RefusalCode;
|
|
@@ -22,6 +22,13 @@ export type TransitionSuccess = {
|
|
|
22
22
|
export type AllocateBody = {
|
|
23
23
|
allocation: number;
|
|
24
24
|
};
|
|
25
|
+
export type AddMetricWarning = {
|
|
26
|
+
code: 'goal-in-some-versions' | 'goal-added-mid-test' | 'placement-unchecked';
|
|
27
|
+
detail?: unknown;
|
|
28
|
+
};
|
|
29
|
+
export type AddMetricResult = TransitionSuccess & {
|
|
30
|
+
warnings: AddMetricWarning[];
|
|
31
|
+
};
|
|
25
32
|
export type AddMetricBody = {
|
|
26
33
|
metric: DocId;
|
|
27
34
|
role: 'secondary' | 'guardrail';
|
|
@@ -57,12 +64,27 @@ export type MakeContentResult = TransitionSuccess & {
|
|
|
57
64
|
}>;
|
|
58
65
|
};
|
|
59
66
|
export type PreflightResult = {
|
|
67
|
+
goalNotes?: Array<{
|
|
68
|
+
metricId: DocId;
|
|
69
|
+
message: string;
|
|
70
|
+
}>;
|
|
60
71
|
checks: Array<{
|
|
61
72
|
code: ReadinessCode;
|
|
62
73
|
ok: boolean;
|
|
63
74
|
detail?: unknown;
|
|
64
75
|
warning?: boolean;
|
|
65
76
|
}>;
|
|
77
|
+
pagePlacements?: Array<{
|
|
78
|
+
arm: string;
|
|
79
|
+
path: string;
|
|
80
|
+
collection?: string;
|
|
81
|
+
id?: DocId | null;
|
|
82
|
+
title?: string;
|
|
83
|
+
published: boolean;
|
|
84
|
+
slug?: string;
|
|
85
|
+
url?: string;
|
|
86
|
+
editorUrl?: string;
|
|
87
|
+
}>;
|
|
66
88
|
placements: Array<{
|
|
67
89
|
collection: string;
|
|
68
90
|
id: DocId;
|
|
@@ -104,6 +126,7 @@ export interface PhaseSummary {
|
|
|
104
126
|
name: string;
|
|
105
127
|
kind: MetricSource['kind'];
|
|
106
128
|
role: 'primary' | 'secondary' | 'guardrail';
|
|
129
|
+
clickGoal?: true;
|
|
107
130
|
resolvedCap?: number | null;
|
|
108
131
|
historyUnits?: number;
|
|
109
132
|
capNotice?: 'no-history';
|
|
@@ -137,10 +160,7 @@ export type SummaryResponse = Record<DocId, {
|
|
|
137
160
|
code: ReadinessCode;
|
|
138
161
|
detail?: unknown;
|
|
139
162
|
};
|
|
140
|
-
health?:
|
|
141
|
-
code: 'version-page-unavailable' | 'version-page-unchecked';
|
|
142
|
-
path: string;
|
|
143
|
-
}>;
|
|
163
|
+
health?: import('./snapshot.js').SnapshotHealth[];
|
|
144
164
|
exposures: number;
|
|
145
165
|
days: number;
|
|
146
166
|
verdict: {
|
|
@@ -174,3 +194,70 @@ export type EndpointContext<TUser extends {
|
|
|
174
194
|
visitorId: string;
|
|
175
195
|
};
|
|
176
196
|
};
|
|
197
|
+
export type ClickKeysResult = {
|
|
198
|
+
keys: Array<{
|
|
199
|
+
key: string;
|
|
200
|
+
arms: string[];
|
|
201
|
+
label: string;
|
|
202
|
+
}>;
|
|
203
|
+
};
|
|
204
|
+
export type PagesResult = {
|
|
205
|
+
docs: Array<{
|
|
206
|
+
id: DocId;
|
|
207
|
+
title: string;
|
|
208
|
+
path: string | null;
|
|
209
|
+
pathKey: string | null;
|
|
210
|
+
published: boolean;
|
|
211
|
+
}>;
|
|
212
|
+
hasNextPage: boolean;
|
|
213
|
+
};
|
|
214
|
+
export type PlacementStatusBody = {
|
|
215
|
+
docs?: Array<{
|
|
216
|
+
collection: string;
|
|
217
|
+
id: DocId;
|
|
218
|
+
}>;
|
|
219
|
+
slugs?: string[];
|
|
220
|
+
};
|
|
221
|
+
export type PageTestStatus = {
|
|
222
|
+
slug: string;
|
|
223
|
+
name: string;
|
|
224
|
+
status: Status;
|
|
225
|
+
role: 'original' | 'version';
|
|
226
|
+
winner: boolean;
|
|
227
|
+
armLabel: string;
|
|
228
|
+
winnerLabel?: string;
|
|
229
|
+
winnerTitle?: string;
|
|
230
|
+
winnerPath?: string;
|
|
231
|
+
originalPath?: string;
|
|
232
|
+
rollout: boolean;
|
|
233
|
+
pagesReleased: boolean;
|
|
234
|
+
};
|
|
235
|
+
export type PlacementStatusResult = {
|
|
236
|
+
docs: Record<string, {
|
|
237
|
+
path: string | null;
|
|
238
|
+
tests: PageTestStatus[];
|
|
239
|
+
}>;
|
|
240
|
+
slugs: Record<string, {
|
|
241
|
+
missing: true;
|
|
242
|
+
} | {
|
|
243
|
+
deleted: true;
|
|
244
|
+
} | {
|
|
245
|
+
name: string;
|
|
246
|
+
status: Status;
|
|
247
|
+
kind: ExperimentKind;
|
|
248
|
+
arms: Array<{
|
|
249
|
+
key: string;
|
|
250
|
+
label: string;
|
|
251
|
+
slot?: string;
|
|
252
|
+
}>;
|
|
253
|
+
winnerArm?: string;
|
|
254
|
+
rollout: boolean;
|
|
255
|
+
}>;
|
|
256
|
+
};
|
|
257
|
+
export type WrapBlockBody = {
|
|
258
|
+
collection: string;
|
|
259
|
+
id: DocId;
|
|
260
|
+
field: string;
|
|
261
|
+
blockId: string;
|
|
262
|
+
experiment: string;
|
|
263
|
+
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,3 +7,6 @@ export * from './admin.js';
|
|
|
7
7
|
export * from './endpoints.js';
|
|
8
8
|
export * from './snapshot.js';
|
|
9
9
|
export * from './reasons.js';
|
|
10
|
+
export type { HostBlock, ExperimentBlock, ExperimentBlockGroup, ResolvedHostBlock, ResolvedExperimentBlocks } from '../blocks/model.js';
|
|
11
|
+
export type { WrapBlockBody } from './endpoints.js';
|
|
12
|
+
export type { SnapshotHealth } from './snapshot.js';
|
package/dist/types/puck.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DocId } from './contracts.js';
|
|
2
|
-
import type { Slot } from '@puckeditor/core';
|
|
2
|
+
import type { ComponentConfig, Config, Plugin, Slot } from '@puckeditor/core';
|
|
3
3
|
export type { Slot } from '@puckeditor/core';
|
|
4
4
|
export type ArmSlot = 'control' | 'variantA' | 'variantB' | 'variantC';
|
|
5
5
|
export type ExperimentRef = {
|
|
@@ -21,3 +21,9 @@ export type ExperimentSlotProps = {
|
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
23
|
export declare const EXPERIMENT_SLOT_TYPE: "ExperimentSlot";
|
|
24
|
+
export interface ExperimentsPuckIntegration {
|
|
25
|
+
name: string;
|
|
26
|
+
components: Record<string, ComponentConfig<ExperimentSlotProps>>;
|
|
27
|
+
categories: Config['categories'];
|
|
28
|
+
plugins?: readonly Plugin[];
|
|
29
|
+
}
|
package/dist/types/snapshot.d.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
export type SnapshotHealth = {
|
|
2
|
+
code: 'version-page-unavailable' | 'version-page-unchecked';
|
|
3
|
+
path: string;
|
|
4
|
+
} | {
|
|
5
|
+
code: 'goal-in-some-versions';
|
|
6
|
+
metricId: string | number;
|
|
7
|
+
name: string;
|
|
8
|
+
} | {
|
|
9
|
+
code: 'placement-unchecked';
|
|
10
|
+
detail: {
|
|
11
|
+
collection?: string;
|
|
12
|
+
id?: string | number;
|
|
13
|
+
path?: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
1
16
|
import type { GaussianPrior } from '@delmaredigital/experiments-stats';
|
|
2
17
|
import type { DocId, MetricSource } from './contracts.js';
|
|
3
18
|
import type { MetricUnavailableReason } from './reasons.js';
|
|
@@ -108,10 +123,8 @@ export interface Snapshot {
|
|
|
108
123
|
guardrailLine?: string;
|
|
109
124
|
worstArm?: string;
|
|
110
125
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
path: string;
|
|
114
|
-
}>;
|
|
126
|
+
goalFairnessNotice?: string;
|
|
127
|
+
health?: SnapshotHealth[];
|
|
115
128
|
contentChanges: Array<{
|
|
116
129
|
at: string;
|
|
117
130
|
doc: {
|
package/dist/types/surface.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export interface TestsSurfaceProps {
|
|
|
7
7
|
returnToTest?: string;
|
|
8
8
|
adminRoute: string;
|
|
9
9
|
apiRoute: string;
|
|
10
|
+
collectionTitles?: Record<string, string>;
|
|
11
|
+
userId?: string | number;
|
|
12
|
+
userCollection?: string;
|
|
10
13
|
isAdmin: boolean;
|
|
11
14
|
pages?: Array<{
|
|
12
15
|
collection: string;
|
|
@@ -14,6 +17,15 @@ export interface TestsSurfaceProps {
|
|
|
14
17
|
id: DocId;
|
|
15
18
|
title: string;
|
|
16
19
|
editorUrl: string;
|
|
20
|
+
editor: 'puck' | 'blocks';
|
|
21
|
+
}>;
|
|
22
|
+
pageCollections?: Array<{
|
|
23
|
+
slug: string;
|
|
24
|
+
label: string;
|
|
25
|
+
contentChecks: boolean;
|
|
26
|
+
copyHint?: string;
|
|
27
|
+
copyUrl?: string;
|
|
28
|
+
copyLabel?: string;
|
|
17
29
|
}>;
|
|
18
30
|
canManage: boolean;
|
|
19
31
|
canReadResults: boolean;
|
|
@@ -22,6 +34,7 @@ export interface TestsSurfaceProps {
|
|
|
22
34
|
metricSources: SerializedMetricSource[];
|
|
23
35
|
admin?: {
|
|
24
36
|
descriptions?: {
|
|
37
|
+
page?: string;
|
|
25
38
|
block?: string;
|
|
26
39
|
document?: string;
|
|
27
40
|
setting?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delmaredigital/payload-experiments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-rc.1",
|
|
4
4
|
"description": "No-code A/B testing for Payload CMS and Next.js: block, page and flag experiments with holdouts, layers, exposure logging and honest statistics inside the admin — no vendor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,12 @@
|
|
|
37
37
|
"import": "./dist/exports/rsc.js",
|
|
38
38
|
"default": "./dist/exports/rsc.js"
|
|
39
39
|
},
|
|
40
|
+
"./blocks": {
|
|
41
|
+
"@delmaredigital/source": "./src/exports/blocks.ts",
|
|
42
|
+
"types": "./dist/exports/blocks.d.ts",
|
|
43
|
+
"import": "./dist/exports/blocks.js",
|
|
44
|
+
"default": "./dist/exports/blocks.js"
|
|
45
|
+
},
|
|
40
46
|
"./client": {
|
|
41
47
|
"@delmaredigital/source": "./src/exports/client.ts",
|
|
42
48
|
"types": "./dist/exports/client.d.ts",
|
|
@@ -120,6 +126,7 @@
|
|
|
120
126
|
},
|
|
121
127
|
"homepage": "https://delmaredigital.com/developers",
|
|
122
128
|
"devDependencies": {
|
|
129
|
+
"@delmaredigital/payload-puck": "0.10.3",
|
|
123
130
|
"payload": "^3.90.2",
|
|
124
131
|
"@payloadcms/db-postgres": "^3.90.2",
|
|
125
132
|
"@payloadcms/ui": "^3.90.2",
|