@delmaredigital/payload-experiments 0.3.0-rc.4 → 0.4.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -27
- 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/TestsDashboardCards.d.ts +1 -0
- package/dist/admin/TestsDashboardCards.js +4 -8
- package/dist/admin/TestsNavLink.d.ts +1 -0
- package/dist/admin/TestsNavLink.js +4 -10
- 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 +18 -2
- package/dist/admin/api.js +47 -2
- package/dist/admin/blocks/BlockPanel.d.ts +4 -0
- package/dist/admin/blocks/BlockPanel.js +199 -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 +46 -0
- package/dist/admin/blocks/formApi.js +159 -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 +9 -0
- package/dist/admin/blocks/statusCache.js +92 -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 +43 -0
- package/dist/admin/copy/blocks.js +64 -0
- package/dist/admin/copy/common.d.ts +1 -0
- package/dist/admin/copy/common.js +1 -1
- 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 +24 -0
- package/dist/admin/copy/pages.js +24 -0
- package/dist/admin/copy/readiness.d.ts +23 -3
- package/dist/admin/copy/readiness.js +24 -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 +4 -0
- package/dist/admin/goals/XpGoalField.js +168 -0
- package/dist/admin/goals/xpGoalField.css +48 -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 +9 -0
- package/dist/admin/paths.js +9 -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 +39 -10
- package/dist/admin/surface/TestsSurface.js +7 -6
- package/dist/admin/surface/contracts.d.ts +6 -5
- package/dist/admin/surface/goals/GoalForm.js +71 -44
- package/dist/admin/surface/goals/GoalsList.js +5 -4
- 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 +3 -3
- package/dist/admin/surface/home.model.js +8 -4
- package/dist/admin/surface/newTest/NewTest.js +56 -17
- package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
- package/dist/admin/surface/newTest/PageWizard.js +214 -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/PageCombobox.d.ts +10 -0
- package/dist/admin/surface/pagePicker/PageCombobox.js +395 -0
- package/dist/admin/surface/pagePicker/pagePicker.css +10 -0
- package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +27 -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 +92 -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 +5 -13
- package/dist/admin/surface/results/useResults.d.ts +20 -1
- package/dist/admin/surface/test/AdvancedSection.js +111 -104
- package/dist/admin/surface/test/GoalSection.js +94 -14
- 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 +5 -9
- 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 +49 -1
- package/dist/admin/surface/test/testPage.css +3 -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 +15 -2
- package/dist/admin/surface/useTestDocument.js +53 -15
- package/dist/admin/surfaceUrls.d.ts +7 -0
- package/dist/admin/surfaceUrls.js +19 -0
- package/dist/admin/theme.css +2 -0
- package/dist/admin/useApi.d.ts +21 -2
- package/dist/admin/useApi.js +3 -5
- package/dist/admin/views/CollectionListRedirect.d.ts +1 -0
- package/dist/admin/views/CollectionListRedirect.js +1 -1
- package/dist/admin/views/ExperimentsRootView.js +28 -133
- package/dist/admin/views/GoalEditRedirect.d.ts +4 -0
- package/dist/admin/views/GoalEditRedirect.js +14 -0
- package/dist/admin/views/GoalsCollectionListView.d.ts +2 -0
- package/dist/admin/views/GoalsCollectionListView.js +4 -0
- package/dist/admin/views/TestsCollectionListView.d.ts +2 -0
- package/dist/admin/views/TestsCollectionListView.js +4 -0
- package/dist/admin/views/TestsNavLinkServer.d.ts +1 -0
- package/dist/admin/views/TestsNavLinkServer.js +1 -1
- package/dist/admin/views/buildSurfaceProps.d.ts +8 -0
- package/dist/admin/views/buildSurfaceProps.js +143 -0
- package/dist/admin/views/renderCollectionList.d.ts +2 -0
- package/dist/admin/views/renderCollectionList.js +34 -0
- package/dist/admin/views/renderTestsSurface.d.ts +3 -0
- package/dist/admin/views/renderTestsSurface.js +34 -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 +21 -7
- 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 +28 -6
- 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/adminRsc.d.ts +3 -0
- package/dist/exports/adminRsc.js +3 -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 +16 -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/goals/validate.d.ts +3 -0
- package/dist/goals/validate.js +14 -0
- package/dist/hooks/pageGuard.d.ts +5 -1
- package/dist/hooks/pageGuard.js +157 -95
- package/dist/hooks/puckGoals.d.ts +3 -0
- package/dist/hooks/puckGoals.js +38 -0
- package/dist/onInit.js +29 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.js +41 -1
- package/dist/plugin.js +50 -43
- package/dist/puck/copy.d.ts +1 -0
- package/dist/puck/copy.js +1 -0
- package/dist/puck/editor/editor.chrome.css +14 -0
- package/dist/puck/editor/editor.css +106 -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 +142 -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 +43 -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 +14 -1
- package/dist/transitions/checkStart.js +92 -8
- package/dist/transitions/decide.js +25 -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 +46 -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/dist/ui/ui.css +4 -0
- package/package.json +8 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { hostBlocks } from '../../blocks/model.js';
|
|
2
|
+
import { BLOCK_COPY, blockDecidedStatus, blockDraftStatus, blockFinishedStatus, blockPosition } from '../copy/blocks.js';
|
|
3
|
+
export function liveStatus(status) {
|
|
4
|
+
return !!status && 'status' in status;
|
|
5
|
+
}
|
|
6
|
+
export function slotLabel(slot, status) {
|
|
7
|
+
return (liveStatus(status) ? status.arms.find((arm)=>arm.key !== 'holdout' && (arm.slot ?? arm.key) === slot)?.label : undefined) || BLOCK_COPY.slotLabels[slot] || slot;
|
|
8
|
+
}
|
|
9
|
+
export function statusSentence(status) {
|
|
10
|
+
if (!liveStatus(status)) return BLOCK_COPY.deleted;
|
|
11
|
+
const winner = status.arms.find((arm)=>arm.key === status.winnerArm);
|
|
12
|
+
const winnerLabel = winner?.label || BLOCK_COPY.slotLabels[status.winnerArm ?? 'control'] || status.winnerArm || BLOCK_COPY.slotLabels.control;
|
|
13
|
+
switch(status.status){
|
|
14
|
+
case 'draft':
|
|
15
|
+
return blockDraftStatus(status.name);
|
|
16
|
+
case 'running':
|
|
17
|
+
return BLOCK_COPY.running;
|
|
18
|
+
case 'stopped':
|
|
19
|
+
return BLOCK_COPY.stopped;
|
|
20
|
+
case 'decided':
|
|
21
|
+
return blockDecidedStatus(winnerLabel, status.rollout);
|
|
22
|
+
case 'archived':
|
|
23
|
+
return status.winnerArm ? blockFinishedStatus(winnerLabel) : BLOCK_COPY.discarded;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function isOrphan(status) {
|
|
27
|
+
return !liveStatus(status) || status.status === 'archived' && !status.winnerArm;
|
|
28
|
+
}
|
|
29
|
+
export function labelText(value) {
|
|
30
|
+
return typeof value === 'string' ? value : value && typeof value === 'object' ? Object.values(value).find((item)=>typeof item === 'string') : undefined;
|
|
31
|
+
}
|
|
32
|
+
export function blockChoices(value, field, blockSlug) {
|
|
33
|
+
return hostBlocks(value).flatMap((block, index)=>{
|
|
34
|
+
if (block.blockType === blockSlug) return [];
|
|
35
|
+
const label = labelText(field.blocks.find((config)=>config.slug === block.blockType)?.labels?.singular) || block.blockType;
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
block,
|
|
39
|
+
index,
|
|
40
|
+
label,
|
|
41
|
+
option: blockPosition(label, index + 1)
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BlockStatus } from './model.js';
|
|
2
|
+
export declare const validBlockSlug: (slug: unknown) => slug is string;
|
|
3
|
+
/** Update a mounted owner's snapshot and invalidate an older pending read. */
|
|
4
|
+
export declare function setBlockStatus(api: string, slug: string, value: BlockStatus): void;
|
|
5
|
+
/** Only status fields own reads. StrictMode replays the same mount token; a
|
|
6
|
+
* different owner after the last cleanup starts fresh, even in the same commit.
|
|
7
|
+
* Deferred eviction removes every entry with no live owner after that commit.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useBlockStatus(api: string, slug: unknown, owner?: boolean): BlockStatus | null;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react';
|
|
2
|
+
import { createApi } from '../api.js';
|
|
3
|
+
const cache = new Map();
|
|
4
|
+
// Subscribers do not create status entries: a label alone owns no cached data.
|
|
5
|
+
const listeners = new Map();
|
|
6
|
+
const keyOf = (api, slug)=>JSON.stringify([
|
|
7
|
+
api,
|
|
8
|
+
slug
|
|
9
|
+
]);
|
|
10
|
+
const publish = (key)=>listeners.get(key)?.callbacks.forEach((listener)=>listener());
|
|
11
|
+
export const validBlockSlug = (slug)=>typeof slug === 'string' && slug.length > 0 && slug.length <= 64;
|
|
12
|
+
/** Update a mounted owner's snapshot and invalidate an older pending read. */ export function setBlockStatus(api, slug, value) {
|
|
13
|
+
const key = keyOf(api, slug), current = cache.get(key);
|
|
14
|
+
if (!current) return;
|
|
15
|
+
current.revision++;
|
|
16
|
+
current.value = value;
|
|
17
|
+
publish(key);
|
|
18
|
+
}
|
|
19
|
+
/** Only status fields own reads. StrictMode replays the same mount token; a
|
|
20
|
+
* different owner after the last cleanup starts fresh, even in the same commit.
|
|
21
|
+
* Deferred eviction removes every entry with no live owner after that commit.
|
|
22
|
+
*/ export function useBlockStatus(api, slug, owner = false) {
|
|
23
|
+
const key = validBlockSlug(slug) ? keyOf(api, slug) : undefined;
|
|
24
|
+
const token = useRef({});
|
|
25
|
+
const subscribe = useCallback((listener)=>{
|
|
26
|
+
if (!key) return ()=>{};
|
|
27
|
+
let current = listeners.get(key);
|
|
28
|
+
if (!current) {
|
|
29
|
+
current = {
|
|
30
|
+
callbacks: new Set()
|
|
31
|
+
};
|
|
32
|
+
listeners.set(key, current);
|
|
33
|
+
}
|
|
34
|
+
current.callbacks.add(listener);
|
|
35
|
+
return ()=>{
|
|
36
|
+
current.callbacks = new Set([
|
|
37
|
+
...current.callbacks
|
|
38
|
+
].filter((item)=>item !== listener));
|
|
39
|
+
if (!current.callbacks.size) listeners.delete(key);
|
|
40
|
+
};
|
|
41
|
+
}, [
|
|
42
|
+
key
|
|
43
|
+
]);
|
|
44
|
+
const status = useSyncExternalStore(subscribe, ()=>{
|
|
45
|
+
const current = key ? cache.get(key) : undefined;
|
|
46
|
+
// A new owner must not sync with the previous mount's snapshot before its
|
|
47
|
+
// effect registers ownership (or starts the fresh read).
|
|
48
|
+
return current && (!owner || current.mounts.has(token.current)) ? current.value : null;
|
|
49
|
+
}, ()=>null);
|
|
50
|
+
useEffect(()=>{
|
|
51
|
+
if (!key || !owner || !validBlockSlug(slug)) return;
|
|
52
|
+
let current = cache.get(key);
|
|
53
|
+
if (!current || !current.owners && !current.mounts.has(token.current)) {
|
|
54
|
+
current = {
|
|
55
|
+
value: null,
|
|
56
|
+
owners: 0,
|
|
57
|
+
mounts: new WeakSet(),
|
|
58
|
+
revision: 0
|
|
59
|
+
};
|
|
60
|
+
cache.set(key, current);
|
|
61
|
+
}
|
|
62
|
+
const owned = current;
|
|
63
|
+
owned.owners++;
|
|
64
|
+
owned.mounts.add(token.current);
|
|
65
|
+
publish(key);
|
|
66
|
+
const revision = owned.revision;
|
|
67
|
+
if (!owned.pending) owned.pending = createApi(api).placementStatus({
|
|
68
|
+
slugs: [
|
|
69
|
+
slug
|
|
70
|
+
]
|
|
71
|
+
}).then((result)=>{
|
|
72
|
+
if (cache.get(key) !== owned || owned.revision !== revision) return;
|
|
73
|
+
owned.value = result.ok ? result.data?.slugs[slug] ?? null : null;
|
|
74
|
+
publish(key);
|
|
75
|
+
});
|
|
76
|
+
return ()=>{
|
|
77
|
+
owned.owners--;
|
|
78
|
+
queueMicrotask(()=>{
|
|
79
|
+
if (!owned.owners && cache.get(key) === owned) {
|
|
80
|
+
cache.delete(key);
|
|
81
|
+
publish(key);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
}, [
|
|
86
|
+
api,
|
|
87
|
+
key,
|
|
88
|
+
slug,
|
|
89
|
+
owner
|
|
90
|
+
]);
|
|
91
|
+
return status;
|
|
92
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useConfig, useDocumentInfo, useForm } from '@payloadcms/ui';
|
|
2
|
+
import { adaptClientBlocksField, rowLocation } from './formApi.js';
|
|
3
|
+
import type { ClientCollectionConfig } from 'payload';
|
|
4
|
+
import type { ExperimentBlock, HostBlock } from '../../blocks/model.js';
|
|
5
|
+
/** Declared so the d.ts build can name Payload UI's form types through public exports. */
|
|
6
|
+
export type BlockFormApi = {
|
|
7
|
+
form: ReturnType<typeof useForm>;
|
|
8
|
+
document: ReturnType<typeof useDocumentInfo>;
|
|
9
|
+
config: ReturnType<typeof useConfig>['config'];
|
|
10
|
+
collection: ClientCollectionConfig | undefined;
|
|
11
|
+
field: ReturnType<typeof adaptClientBlocksField> | undefined;
|
|
12
|
+
block: ExperimentBlock | undefined;
|
|
13
|
+
manager: boolean;
|
|
14
|
+
replace: (rows: HostBlock[]) => Promise<void>;
|
|
15
|
+
location: ReturnType<typeof rowLocation>;
|
|
16
|
+
};
|
|
17
|
+
export declare function useBlockForm(path: string): BlockFormApi;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useAuth, useConfig, useDocumentInfo, useForm, useFormFields, useServerFunctions } from '@payloadcms/ui';
|
|
3
|
+
import { adaptClientBlocksField, findClientBlocksField, replaceBlockRow, rowLocation } from './formApi.js';
|
|
4
|
+
export function useBlockForm(path) {
|
|
5
|
+
const form = useForm(), document = useDocumentInfo(), { config, getEntityConfig } = useConfig();
|
|
6
|
+
const { getFormState } = useServerFunctions(), { permissions } = useAuth();
|
|
7
|
+
// useForm's getters are stable; subscribe so changed slug/row paths are read again.
|
|
8
|
+
useFormFields(([fields])=>fields);
|
|
9
|
+
const location = rowLocation(path);
|
|
10
|
+
const collection = document.collectionSlug ? getEntityConfig({
|
|
11
|
+
collectionSlug: document.collectionSlug
|
|
12
|
+
}) : undefined;
|
|
13
|
+
const clientField = location && collection ? findClientBlocksField(collection.fields, location.field) : undefined;
|
|
14
|
+
const field = useMemo(()=>clientField ? adaptClientBlocksField(clientField, config) : undefined, [
|
|
15
|
+
clientField,
|
|
16
|
+
config
|
|
17
|
+
]);
|
|
18
|
+
const block = location ? form.getDataByPath(`${location.field}.${location.index}`) : undefined;
|
|
19
|
+
const testPermissions = permissions?.collections?.['xp-experiments'];
|
|
20
|
+
const manager = testPermissions?.update === true;
|
|
21
|
+
async function replace(rows) {
|
|
22
|
+
if (!location || !document.collectionSlug) return;
|
|
23
|
+
await replaceBlockRow({
|
|
24
|
+
form,
|
|
25
|
+
getFormState,
|
|
26
|
+
collectionSlug: document.collectionSlug,
|
|
27
|
+
id: document.id,
|
|
28
|
+
...location,
|
|
29
|
+
rows,
|
|
30
|
+
expectedBlock: block,
|
|
31
|
+
docPermissions: document.docPermissions,
|
|
32
|
+
docPreferences: await document.getDocPreferences?.()
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
form,
|
|
37
|
+
document,
|
|
38
|
+
config,
|
|
39
|
+
collection,
|
|
40
|
+
field,
|
|
41
|
+
block,
|
|
42
|
+
manager,
|
|
43
|
+
replace,
|
|
44
|
+
location
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AddMetricWarning } from '../../types/endpoints.js';
|
|
1
2
|
export declare const ACTIONS_COPY: {
|
|
2
3
|
readonly start: "Start";
|
|
3
4
|
readonly restart: "Start a new round";
|
|
@@ -70,3 +71,4 @@ export declare const CONTENT_ENTRY_REFUSALS: {
|
|
|
70
71
|
};
|
|
71
72
|
readonly fallback: "This page can't be changed right now. Reload to see its current state.";
|
|
72
73
|
};
|
|
74
|
+
export declare function addMetricWarningCopy(warning: AddMetricWarning): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asymmetricGoalsCopy, placementUncheckedCopy } from './readiness.js';
|
|
1
2
|
export const ACTIONS_COPY = {
|
|
2
3
|
start: 'Start',
|
|
3
4
|
restart: 'Start a new round',
|
|
@@ -70,3 +71,11 @@ export const CONTENT_ENTRY_REFUSALS = {
|
|
|
70
71
|
},
|
|
71
72
|
fallback: ACTIONS_COPY.contentEntryFallback
|
|
72
73
|
};
|
|
74
|
+
export function addMetricWarningCopy(warning) {
|
|
75
|
+
if (warning.code === 'goal-added-mid-test') return "Clicks before this goal was added aren't counted (the same for every version).";
|
|
76
|
+
if (warning.code === 'placement-unchecked') {
|
|
77
|
+
const path = warning.detail?.path;
|
|
78
|
+
return placementUncheckedCopy(typeof path === 'string' ? path : 'This page');
|
|
79
|
+
}
|
|
80
|
+
return asymmetricGoalsCopy(warning.detail);
|
|
81
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const BLOCK_COPY: {
|
|
2
|
+
testBlock: string;
|
|
3
|
+
pick: string;
|
|
4
|
+
empty: string;
|
|
5
|
+
blockChanged: string;
|
|
6
|
+
replace: string;
|
|
7
|
+
cancel: string;
|
|
8
|
+
retry: string;
|
|
9
|
+
saveFirst: string;
|
|
10
|
+
setup: string;
|
|
11
|
+
copy: string;
|
|
12
|
+
confirmCopy: string;
|
|
13
|
+
unwrap: string;
|
|
14
|
+
removeAndUnwrap: string;
|
|
15
|
+
deleteAndUnwrap: string;
|
|
16
|
+
alreadyDeleted: string;
|
|
17
|
+
noLongerDraft: string;
|
|
18
|
+
finishUnwrap: string;
|
|
19
|
+
deleted: string;
|
|
20
|
+
discarded: string;
|
|
21
|
+
running: string;
|
|
22
|
+
stopped: string;
|
|
23
|
+
slotLabels: Record<string, string>;
|
|
24
|
+
wrapRefusals: Record<string, string>;
|
|
25
|
+
};
|
|
26
|
+
export declare const blockPosition: (label: string, position: number) => string;
|
|
27
|
+
export declare const blockTestName: (title: string, label: string) => string;
|
|
28
|
+
export declare const blockDraftStatus: (name: string) => string;
|
|
29
|
+
export declare const blockDecidedStatus: (winner: string, rollout: boolean) => string;
|
|
30
|
+
export type BlockLinkedCopy = {
|
|
31
|
+
before: string;
|
|
32
|
+
link: string;
|
|
33
|
+
after: string;
|
|
34
|
+
};
|
|
35
|
+
export declare const blockRunningUnwrap: BlockLinkedCopy;
|
|
36
|
+
export declare const blockDecidedUnwrap: BlockLinkedCopy;
|
|
37
|
+
export declare const blockFinishedCopy: (winner: string) => BlockLinkedCopy;
|
|
38
|
+
export declare const blockFinishedStatus: (winner: string) => string;
|
|
39
|
+
export declare const blockDeleteConfirmation: (name: string) => string;
|
|
40
|
+
export declare const blockUnwrapConfirmation: (name: string) => string;
|
|
41
|
+
export declare const blockUnwrapNotice: (drafts: boolean, deleted: boolean) => string;
|
|
42
|
+
export declare const blockSaveNotice: (drafts: boolean) => "Save a draft of this page to add the test." | "Save this page to add the test.";
|
|
43
|
+
export declare const blockSyncNotice: (drafts: boolean) => "Versions were updated to match the test. Save draft to keep them." | "Versions were updated to match the test. Save to keep them.";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const BLOCK_COPY = {
|
|
2
|
+
testBlock: 'Test a block on this page',
|
|
3
|
+
pick: 'Choose a block',
|
|
4
|
+
empty: 'There are no blocks on this page to test yet. Add one, save, then come back.',
|
|
5
|
+
blockChanged: 'This block changed while you were working. Nothing was replaced. Try again.',
|
|
6
|
+
replace: 'Replace',
|
|
7
|
+
cancel: 'Cancel',
|
|
8
|
+
retry: 'Try again',
|
|
9
|
+
saveFirst: 'Save your changes, then try again.',
|
|
10
|
+
setup: 'Set up the test',
|
|
11
|
+
copy: 'Copy the Original into this version',
|
|
12
|
+
confirmCopy: 'Replace this version’s content with a copy of the Original? Edits you made to this version will be lost.',
|
|
13
|
+
unwrap: 'Unwrap',
|
|
14
|
+
removeAndUnwrap: 'Remove the test and unwrap',
|
|
15
|
+
deleteAndUnwrap: 'Delete and unwrap',
|
|
16
|
+
alreadyDeleted: 'This test was already deleted. Reload the page.',
|
|
17
|
+
noLongerDraft: 'This test is no longer a draft. Reload the page.',
|
|
18
|
+
finishUnwrap: 'The test was removed; use Unwrap to finish.',
|
|
19
|
+
deleted: "This block's test was deleted; the page shows the Original.",
|
|
20
|
+
discarded: 'This test has ended; the page shows the Original.',
|
|
21
|
+
running: "Running: visitors see different versions. Edit a version's tab to change it; the change is noted on the results.",
|
|
22
|
+
stopped: 'Stopped: visitors see the Original.',
|
|
23
|
+
slotLabels: {
|
|
24
|
+
control: 'Original',
|
|
25
|
+
variantA: 'Variant A',
|
|
26
|
+
variantB: 'Variant B',
|
|
27
|
+
variantC: 'Variant C'
|
|
28
|
+
},
|
|
29
|
+
wrapRefusals: {
|
|
30
|
+
'wrap-target-missing': 'Reload the page and try again.',
|
|
31
|
+
'locked-placement': 'Another editor has this page open. Try again when it is unlocked.',
|
|
32
|
+
'invalid-status': 'This action is not available in the current status.',
|
|
33
|
+
'unknown-collection': "That page type isn't set up for tests.",
|
|
34
|
+
'invalid-body': 'Choose a block test.'
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export const blockPosition = (label, position)=>`${label} (block ${position})`;
|
|
38
|
+
export const blockTestName = (title, label)=>`${title}: ${label}`;
|
|
39
|
+
export const blockDraftStatus = (name)=>`This block is being tested in the draft test ${name}. Visitors see the Original until it starts.`;
|
|
40
|
+
export const blockDecidedStatus = (winner, rollout)=>`Decided: visitors see ${winner}${rollout ? '; a holdout still sees the Original' : ''}.`;
|
|
41
|
+
export const blockRunningUnwrap = {
|
|
42
|
+
before: 'Stop ',
|
|
43
|
+
link: 'the test',
|
|
44
|
+
after: ' to unwrap.'
|
|
45
|
+
};
|
|
46
|
+
export const blockDecidedUnwrap = {
|
|
47
|
+
before: 'To put the winner into this page, use "Make it the content" on ',
|
|
48
|
+
link: 'the test',
|
|
49
|
+
after: '.'
|
|
50
|
+
};
|
|
51
|
+
export const blockFinishedCopy = (winner)=>({
|
|
52
|
+
before: `Finished: visitors see ${winner}. Use "Make it the content" on `,
|
|
53
|
+
link: 'the test',
|
|
54
|
+
after: ' to replace this block.'
|
|
55
|
+
});
|
|
56
|
+
export const blockFinishedStatus = (winner)=>{
|
|
57
|
+
const copy = blockFinishedCopy(winner);
|
|
58
|
+
return copy.before + copy.link + copy.after;
|
|
59
|
+
};
|
|
60
|
+
export const blockDeleteConfirmation = (name)=>`Delete the draft test ${name} and put the Original back in its place? Deleting the test can't be undone.`;
|
|
61
|
+
export const blockUnwrapConfirmation = (name)=>`Put the Original back in this block's place? The test ${name} and its results are kept; it can't be started again.`;
|
|
62
|
+
export const blockUnwrapNotice = (drafts, deleted)=>`${deleted ? 'The draft test was deleted and the block unwrapped.' : 'The block was unwrapped.'} ${drafts ? 'Save draft' : 'Save'} to keep it.`;
|
|
63
|
+
export const blockSaveNotice = (drafts)=>drafts ? 'Save a draft of this page to add the test.' : 'Save this page to add the test.';
|
|
64
|
+
export const blockSyncNotice = (drafts)=>drafts ? 'Versions were updated to match the test. Save draft to keep them.' : 'Versions were updated to match the test. Save to keep them.';
|
|
@@ -22,6 +22,7 @@ export declare const LOCKED_MESSAGES: {
|
|
|
22
22
|
};
|
|
23
23
|
export declare function refusalMessage(code: string | undefined, status: number, message?: string, options?: {
|
|
24
24
|
notFound?: string;
|
|
25
|
+
forbidden?: string;
|
|
25
26
|
codes?: Readonly<Record<string, string>>;
|
|
26
27
|
fallback?: string;
|
|
27
28
|
}): string;
|
|
@@ -28,6 +28,6 @@ export function refusalMessage(code, status, message, options) {
|
|
|
28
28
|
if (code === 'not-found' || status === 404) return options?.notFound ?? API_MESSAGES.notFound;
|
|
29
29
|
if (code === 'commit-unverified') return API_MESSAGES.commitUnverified;
|
|
30
30
|
if (code === 'not-implemented' || status === 501) return API_MESSAGES.notImplemented;
|
|
31
|
-
if (code === 'forbidden' || status === 403) return API_MESSAGES.forbidden;
|
|
31
|
+
if (code === 'forbidden' || status === 403) return options?.forbidden ?? API_MESSAGES.forbidden;
|
|
32
32
|
return options?.fallback ?? (message || API_MESSAGES.failed);
|
|
33
33
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const GOALS_COPY: {
|
|
2
|
+
readonly slugRule: "Use 1–64 lowercase letters, numbers, underscores or hyphens, starting with a letter or number.";
|
|
3
|
+
readonly duplicateSlug: "Another goal already uses this short name. Change it under Advanced.";
|
|
2
4
|
readonly title: "Goals";
|
|
3
5
|
readonly goal: "Goal";
|
|
4
6
|
readonly newGoal: "New goal";
|
package/dist/admin/copy/goals.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { CLICK_GOAL_COPY } from '../../goals/picker/copy.js';
|
|
1
2
|
export const GOALS_COPY = {
|
|
3
|
+
slugRule: CLICK_GOAL_COPY.slugRule,
|
|
4
|
+
duplicateSlug: CLICK_GOAL_COPY.duplicateSlug,
|
|
2
5
|
title: 'Goals',
|
|
3
6
|
goal: 'Goal',
|
|
4
7
|
newGoal: 'New goal',
|
|
@@ -38,7 +41,7 @@ export const GOALS_COPY = {
|
|
|
38
41
|
remove: 'Delete goal',
|
|
39
42
|
thresholdError: 'Enter a percentage of zero or more, or leave blank for 5%.',
|
|
40
43
|
deleting: 'Deleting…',
|
|
41
|
-
saveError:
|
|
44
|
+
saveError: CLICK_GOAL_COPY.saveError,
|
|
42
45
|
deleteError: "Couldn't delete the goal. Try again.",
|
|
43
46
|
breadcrumb: 'Breadcrumb',
|
|
44
47
|
missing: 'This goal no longer exists.',
|
|
@@ -2,6 +2,16 @@ export declare const NEW_TEST_COPY: {
|
|
|
2
2
|
readonly question: "What do you want to test?";
|
|
3
3
|
readonly block: "A block on a page";
|
|
4
4
|
readonly setting: "A setting";
|
|
5
|
+
readonly page: "A whole page";
|
|
6
|
+
readonly pageMeaning: "Show different versions of a whole page at the same address, and see which one works better.";
|
|
7
|
+
readonly collection: "Collection";
|
|
8
|
+
readonly original: "Original";
|
|
9
|
+
readonly originalHelp: "Choose the page visitors see today.";
|
|
10
|
+
readonly review: "Review";
|
|
11
|
+
readonly backToTest: "Back to the test";
|
|
12
|
+
readonly finishSetup: "Finish setup";
|
|
13
|
+
readonly start: "Start";
|
|
14
|
+
readonly blocksHandoff: "Pick the block to test with 'Test a block on this page'.";
|
|
5
15
|
readonly blockMeaning: "Compare different versions of content on a page.";
|
|
6
16
|
readonly settingMeaning: "Compare different choices for a setting on your site.";
|
|
7
17
|
readonly unavailable: "A developer needs to set up pages or settings before you can create a test.";
|
|
@@ -2,6 +2,16 @@ export const NEW_TEST_COPY = {
|
|
|
2
2
|
question: 'What do you want to test?',
|
|
3
3
|
block: 'A block on a page',
|
|
4
4
|
setting: 'A setting',
|
|
5
|
+
page: 'A whole page',
|
|
6
|
+
pageMeaning: 'Show different versions of a whole page at the same address, and see which one works better.',
|
|
7
|
+
collection: 'Collection',
|
|
8
|
+
original: 'Original',
|
|
9
|
+
originalHelp: 'Choose the page visitors see today.',
|
|
10
|
+
review: 'Review',
|
|
11
|
+
backToTest: 'Back to the test',
|
|
12
|
+
finishSetup: 'Finish setup',
|
|
13
|
+
start: 'Start',
|
|
14
|
+
blocksHandoff: "Pick the block to test with 'Test a block on this page'.",
|
|
5
15
|
blockMeaning: 'Compare different versions of content on a page.',
|
|
6
16
|
settingMeaning: 'Compare different choices for a setting on your site.',
|
|
7
17
|
unavailable: 'A developer needs to set up pages or settings before you can create a test.',
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const PAGE_COPY: {
|
|
2
|
+
readonly more: "More";
|
|
3
|
+
readonly loading: "Loading pages…";
|
|
4
|
+
readonly empty: "There are no pages here yet.";
|
|
5
|
+
readonly error: "Couldn't load pages. Try again.";
|
|
6
|
+
readonly retry: "Try again";
|
|
7
|
+
readonly draft: "Draft";
|
|
8
|
+
readonly noPath: "This page has no address yet.";
|
|
9
|
+
readonly choose: "Choose a page";
|
|
10
|
+
readonly where: "Where it shows";
|
|
11
|
+
readonly open: "Open";
|
|
12
|
+
readonly preview: "Preview";
|
|
13
|
+
readonly contentChecksOff: "Content checks are off for this page type.";
|
|
14
|
+
readonly duplicateStep: "Choose Duplicate from the document's actions. Keep the Original's title on the copy, unless the title is what you're testing.";
|
|
15
|
+
readonly ownAddress: "Give the copy its own address and save it as a draft. Publish it when it's ready; the test can't start until it is.";
|
|
16
|
+
readonly thenChoose: "Then choose the page you made.";
|
|
17
|
+
};
|
|
18
|
+
export declare const usedPageCopy: (label: string) => string;
|
|
19
|
+
export declare const usedAddressCopy: (label: string, path: string, collection: string) => string;
|
|
20
|
+
export declare const openOriginalCopy: (collection: string) => string;
|
|
21
|
+
export declare const openCollectionCopy: (collection: string) => string;
|
|
22
|
+
export declare const pageStepCopy: (label: string) => string;
|
|
23
|
+
export declare const pageNotFoundCopy: (path: string, collection: string) => string;
|
|
24
|
+
export declare const pageFieldCopy: (label: string) => string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const PAGE_COPY = {
|
|
2
|
+
more: 'More',
|
|
3
|
+
loading: 'Loading pages…',
|
|
4
|
+
empty: 'There are no pages here yet.',
|
|
5
|
+
error: "Couldn't load pages. Try again.",
|
|
6
|
+
retry: 'Try again',
|
|
7
|
+
draft: 'Draft',
|
|
8
|
+
noPath: 'This page has no address yet.',
|
|
9
|
+
choose: 'Choose a page',
|
|
10
|
+
where: 'Where it shows',
|
|
11
|
+
open: 'Open',
|
|
12
|
+
preview: 'Preview',
|
|
13
|
+
contentChecksOff: 'Content checks are off for this page type.',
|
|
14
|
+
duplicateStep: "Choose Duplicate from the document's actions. Keep the Original's title on the copy, unless the title is what you're testing.",
|
|
15
|
+
ownAddress: "Give the copy its own address and save it as a draft. Publish it when it's ready; the test can't start until it is.",
|
|
16
|
+
thenChoose: 'Then choose the page you made.'
|
|
17
|
+
};
|
|
18
|
+
export const usedPageCopy = (label)=>`This is ${label}.`;
|
|
19
|
+
export const usedAddressCopy = (label, path, collection)=>`Same address as ${label} (${path}). Change this page's address in ${collection}, publish it, then choose it.`;
|
|
20
|
+
export const openOriginalCopy = (collection)=>`Open the Original in ${collection}.`;
|
|
21
|
+
export const openCollectionCopy = (collection)=>`Open ${collection}`;
|
|
22
|
+
export const pageStepCopy = (label)=>`A page for ${label}`;
|
|
23
|
+
export const pageNotFoundCopy = (path, collection)=>`${path} isn't in ${collection}. Choose a page.`;
|
|
24
|
+
export const pageFieldCopy = (label)=>`Page for ${label}`;
|
|
@@ -25,6 +25,7 @@ export declare const READINESS_COPY: {
|
|
|
25
25
|
readonly everyone: "Everyone already takes part.";
|
|
26
26
|
readonly participation: "How many people take part (%)";
|
|
27
27
|
readonly noPlacements: "Not on a page yet. Open a page, add an Experiment block and choose this test.";
|
|
28
|
+
readonly placementRemoved: "The block for this test was removed from its page, so it can't start again. Its results are kept. To test the block again, start a new test from the page.";
|
|
28
29
|
readonly noPoint: "Choose the setting to test.";
|
|
29
30
|
readonly busy: "Another test is already running on this setting. Stop it first.";
|
|
30
31
|
readonly noGoal: "Choose a goal.";
|
|
@@ -35,7 +36,8 @@ export declare const READINESS_COPY: {
|
|
|
35
36
|
readonly differentPeople: "This test's group counts different people. Create the test again without the group.";
|
|
36
37
|
readonly uncheckedPages: "Version pages aren't checked; make sure each one is published.";
|
|
37
38
|
readonly chooseDifferentPage: "Choose a different page";
|
|
38
|
-
readonly
|
|
39
|
+
readonly choosePages: "Choose pages";
|
|
40
|
+
readonly noTargets: "Choose a page for each version.";
|
|
39
41
|
readonly pageVisitors: "Whole-page tests count visitors. Create the test again counting visitors.";
|
|
40
42
|
readonly blockPathRule: "Remove the page-path rule in Who takes part; page block tests run on their page.";
|
|
41
43
|
readonly removeRule: "Remove rule";
|
|
@@ -46,8 +48,12 @@ export declare const READINESS_COPY: {
|
|
|
46
48
|
readonly attributeFallback: "this attribute";
|
|
47
49
|
};
|
|
48
50
|
export declare const unsupportedContentCopy: (page: string) => string;
|
|
49
|
-
export declare const pageInUseCopy: (path: string) => string;
|
|
50
|
-
export declare const unpublishedPageCopy: (path: string
|
|
51
|
+
export declare const pageInUseCopy: (path: string, version?: boolean) => string;
|
|
52
|
+
export declare const unpublishedPageCopy: (path: string, context?: {
|
|
53
|
+
original: boolean;
|
|
54
|
+
found: boolean;
|
|
55
|
+
collection: string;
|
|
56
|
+
}) => string;
|
|
51
57
|
/** Old responses may include raw host errors; unknown strings use authored copy. */
|
|
52
58
|
export declare const placementFailureCopy: (reason: unknown) => "Couldn't check where this test is used. Try again." | "You don't have permission to check where this test is used." | "A page used by this test no longer exists. Choose another page.";
|
|
53
59
|
export declare const nextCopy: (sentence: string) => string;
|
|
@@ -65,3 +71,17 @@ export declare const runningCopy: (date: string | null, people: number | null, d
|
|
|
65
71
|
export declare const pageGuardCopy: (name: string, status: "running" | "decided" | "archived", role?: "winner" | "original") => string;
|
|
66
72
|
export declare const versionPageUnavailableCopy: (path: string, status?: string) => string;
|
|
67
73
|
export declare const versionPageUncheckedCopy: (path: string, status?: string) => string;
|
|
74
|
+
export declare const sameAddressCopy: (label: string, other: string, path: string, collection?: string) => string;
|
|
75
|
+
export declare const identicalVersionCopy: (version: string) => string;
|
|
76
|
+
/** Keep named actions aligned with the actual transition buttons. */
|
|
77
|
+
export declare function placementGuardCopy(page: {
|
|
78
|
+
name: string;
|
|
79
|
+
status: string;
|
|
80
|
+
}, block: {
|
|
81
|
+
name: string;
|
|
82
|
+
status: string;
|
|
83
|
+
}, preExisting: boolean): string;
|
|
84
|
+
export declare function interactingTestsCopy(page: string, state: string, name: string, version?: boolean): string;
|
|
85
|
+
export declare const goalInSomeVersionsCopy: (name?: string) => string;
|
|
86
|
+
export declare function asymmetricGoalsCopy(detail: unknown): string;
|
|
87
|
+
export declare const placementUncheckedCopy: (page: string) => string;
|
|
@@ -25,6 +25,7 @@ export const READINESS_COPY = {
|
|
|
25
25
|
everyone: 'Everyone already takes part.',
|
|
26
26
|
participation: 'How many people take part (%)',
|
|
27
27
|
noPlacements: 'Not on a page yet. Open a page, add an Experiment block and choose this test.',
|
|
28
|
+
placementRemoved: "The block for this test was removed from its page, so it can't start again. Its results are kept. To test the block again, start a new test from the page.",
|
|
28
29
|
noPoint: 'Choose the setting to test.',
|
|
29
30
|
busy: 'Another test is already running on this setting. Stop it first.',
|
|
30
31
|
noGoal: 'Choose a goal.',
|
|
@@ -35,7 +36,8 @@ export const READINESS_COPY = {
|
|
|
35
36
|
differentPeople: "This test's group counts different people. Create the test again without the group.",
|
|
36
37
|
uncheckedPages: "Version pages aren't checked; make sure each one is published.",
|
|
37
38
|
chooseDifferentPage: 'Choose a different page',
|
|
38
|
-
|
|
39
|
+
choosePages: 'Choose pages',
|
|
40
|
+
noTargets: 'Choose a page for each version.',
|
|
39
41
|
pageVisitors: 'Whole-page tests count visitors. Create the test again counting visitors.',
|
|
40
42
|
blockPathRule: 'Remove the page-path rule in Who takes part; page block tests run on their page.',
|
|
41
43
|
// Next steps repeat the button label verbatim (testPage.ts removeRule).
|
|
@@ -47,8 +49,8 @@ export const READINESS_COPY = {
|
|
|
47
49
|
attributeFallback: 'this attribute'
|
|
48
50
|
};
|
|
49
51
|
export const unsupportedContentCopy = (page)=>`${page} has more than one language, so Make it the content isn't available for it; you'll copy the winning version by hand.`;
|
|
50
|
-
export const pageInUseCopy = (path)
|
|
51
|
-
export const unpublishedPageCopy = (path)
|
|
52
|
+
export const pageInUseCopy = (path, version = false)=>version ? `${path} is already in another running or decided test. Make a new copy of the Original for this version, or finish that test first.` : `Another running or decided test already uses ${path}. Choose a different page, or finish that test first.`;
|
|
53
|
+
export const unpublishedPageCopy = (path, context)=>!context ? `${path} isn't a published page yet. Publish it, or choose another page.` : context.original ? `${path} isn't published, so it can't be the Original. Publish it, or choose another page.` : context.found ? `${path} is still a draft. Publish it in ${context.collection}, then start the test.` : `${path} isn't published yet, or no page has that address. Publish it in ${context.collection}, or choose another page.`;
|
|
52
54
|
/** Old responses may include raw host errors; unknown strings use authored copy. */ export const placementFailureCopy = (reason)=>PLACEMENT_FAILURE_COPY[typeof reason === 'string' && Object.hasOwn(PLACEMENT_FAILURE_COPY, reason) ? reason : 'placements-failed'];
|
|
53
55
|
const withoutPeriod = (value)=>value.trim().replace(/[.!?]+$/, '');
|
|
54
56
|
export const nextCopy = (sentence)=>`Next: ${sentence}`;
|
|
@@ -69,3 +71,22 @@ export const runningCopy = (date, people, days)=>[
|
|
|
69
71
|
/** After archive the guard protects the winner and the Original; only the winner page is "the winner". */ export const pageGuardCopy = (name, status, role = 'winner')=>status === 'archived' ? role === 'original' ? `This page's address now shows the winner of the finished test "${name}". To free it, open the test and choose "Release pages".` : `This page is shown at other addresses because the finished test "${name}" made it the winner. To free it, open the test and choose "Release pages".` : status === 'decided' ? `This page is part of the decided test "${name}". Choose "Make it the content" or "Discard winner" on the test before you delete, rename or unpublish it.` : `This page is part of the running test "${name}". Stop the test before you delete, rename or unpublish it.`;
|
|
70
72
|
export const versionPageUnavailableCopy = (path, status = 'running')=>`${path} isn't published, so visitors assigned to it see an error. Republish it, or ${status === 'decided' ? 'discard the winner' : 'stop the test'}.`;
|
|
71
73
|
export const versionPageUncheckedCopy = (path, status = 'running')=>`Couldn't check whether ${path} is published. Check that it is still published, or ${status === 'decided' ? 'discard the winner' : 'stop the test'}.`;
|
|
74
|
+
export const sameAddressCopy = (label, other, path, collection)=>`${label} has the same address (${path}) as ${other}. Choose a different page.${collection ? ` Give the copy its own address in ${collection}, publish it, then choose it.` : ''}`;
|
|
75
|
+
export const identicalVersionCopy = (version)=>`${version} looks the same as the Original, so the test would compare nothing. Change it before you start.`;
|
|
76
|
+
/** Keep named actions aligned with the actual transition buttons. */ export function placementGuardCopy(page, block, preExisting) {
|
|
77
|
+
const action = (test)=>test.status === 'running' ? `stop ${test.name}` : `choose "Make it the content" or "Discard winner" on ${test.name}`;
|
|
78
|
+
return preExisting ? `This page is in two tests that change it, ${page.name} and ${block.name}. Before you publish, ${action(page)} or ${action(block)}.` : `This page is part of the ${page.status} test ${page.name}, so a block from another test can't be added to it. Finish one of the tests first.`;
|
|
79
|
+
}
|
|
80
|
+
export function interactingTestsCopy(page, state, name, version = false) {
|
|
81
|
+
return `This test uses ${page}, which the ${state} test ${name} also changes. Finish one of them first${version ? `, or remove its block from ${page}` : ''}.`;
|
|
82
|
+
}
|
|
83
|
+
export const goalInSomeVersionsCopy = (name = 'This goal')=>`${name} appears in some versions but not all, so the comparison wouldn't be fair. Add it to every version, or choose another goal.`;
|
|
84
|
+
export function asymmetricGoalsCopy(detail) {
|
|
85
|
+
return (Array.isArray(detail) && detail.length ? detail : [
|
|
86
|
+
detail
|
|
87
|
+
]).map((item)=>{
|
|
88
|
+
const name = item && typeof item === 'object' && 'name' in item ? item.name : undefined;
|
|
89
|
+
return goalInSomeVersionsCopy(typeof name === 'string' && name.trim() ? name.trim() : undefined);
|
|
90
|
+
}).join('\n');
|
|
91
|
+
}
|
|
92
|
+
export const placementUncheckedCopy = (page)=>`${page} couldn't be checked, so the test can't confirm every version shows the same goals. Check that you can open it.`;
|
|
@@ -22,6 +22,7 @@ export declare const RESULTS_COPY: {
|
|
|
22
22
|
};
|
|
23
23
|
readonly qualityOK: "No data quality problems found.";
|
|
24
24
|
readonly previews: "Preview links";
|
|
25
|
+
readonly clickPrimary: "A click shows interest, not the result you want. Add that result, like a sign-up or a gift, as another goal to check that more clicks lead to more of it.";
|
|
25
26
|
readonly releasePages: "Release pages";
|
|
26
27
|
readonly releasePagesBody: "Every address shows its own page again. The result stays recorded.";
|
|
27
28
|
readonly you: "you";
|
|
@@ -22,6 +22,7 @@ export const RESULTS_COPY = {
|
|
|
22
22
|
},
|
|
23
23
|
qualityOK: 'No data quality problems found.',
|
|
24
24
|
previews: 'Preview links',
|
|
25
|
+
clickPrimary: 'A click shows interest, not the result you want. Add that result, like a sign-up or a gift, as another goal to check that more clicks lead to more of it.',
|
|
25
26
|
releasePages: 'Release pages',
|
|
26
27
|
releasePagesBody: 'Every address shows its own page again. The result stays recorded.',
|
|
27
28
|
you: 'you',
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export declare const TEST_PAGE: {
|
|
2
|
+
readonly clickPageGroup: "On this test's page";
|
|
3
|
+
readonly clickGoalsNote: "Goals inside linked documents aren't shown. Goals added in code (data-xp-goal) work but aren't listed here.";
|
|
4
|
+
readonly clickGoalHelp: "A click shows interest, not the result you want.";
|
|
2
5
|
readonly staleEdit: "Someone else changed this test. Reload to see their changes.";
|
|
3
6
|
readonly reload: "Reload";
|
|
4
7
|
readonly loading: "Loading test…";
|
|
@@ -54,6 +57,7 @@ export declare const TEST_PAGE: {
|
|
|
54
57
|
readonly split: "Split evenly";
|
|
55
58
|
readonly remove: "Remove version";
|
|
56
59
|
readonly invalidShare: "Use a whole number from 0 to 100.";
|
|
60
|
+
readonly holdoutPrefix: "Holdout: ";
|
|
57
61
|
readonly allocation: "How many people take part";
|
|
58
62
|
readonly traffic: "How people are split";
|
|
59
63
|
readonly goal: "Goal";
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export const TEST_PAGE = {
|
|
2
|
+
clickPageGroup: "On this test's page",
|
|
3
|
+
clickGoalsNote: "Goals inside linked documents aren't shown. Goals added in code (data-xp-goal) work but aren't listed here.",
|
|
4
|
+
clickGoalHelp: 'A click shows interest, not the result you want.',
|
|
2
5
|
staleEdit: 'Someone else changed this test. Reload to see their changes.',
|
|
3
6
|
reload: 'Reload',
|
|
4
7
|
loading: 'Loading test…',
|
|
@@ -54,6 +57,7 @@ export const TEST_PAGE = {
|
|
|
54
57
|
split: 'Split evenly',
|
|
55
58
|
remove: 'Remove version',
|
|
56
59
|
invalidShare: 'Use a whole number from 0 to 100.',
|
|
60
|
+
holdoutPrefix: 'Holdout: ',
|
|
57
61
|
allocation: 'How many people take part',
|
|
58
62
|
traffic: 'How people are split',
|
|
59
63
|
goal: 'Goal',
|