@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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const placementDetail = (detail)=>detail && typeof detail === 'object' && !Array.isArray(detail) ? detail : {};
|
|
2
|
+
export function placementDetailKey(value) {
|
|
3
|
+
const detail = placementDetail(value);
|
|
4
|
+
return JSON.stringify([
|
|
5
|
+
detail.collection,
|
|
6
|
+
detail.id == null ? null : String(detail.id),
|
|
7
|
+
detail.path
|
|
8
|
+
]);
|
|
9
|
+
}
|
|
10
|
+
export const fallbackPlacementLabel = (value)=>({
|
|
11
|
+
title: placementDetail(value).path || 'This page'
|
|
12
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PreflightResult } from '../../types/endpoints.js';
|
|
2
|
+
import type { PlacementLabel } from './placementLabels.js';
|
|
3
|
+
export { placementDetailKey } from './placementLabels.js';
|
|
4
|
+
/** The view owns a cache, including misses, so repeated warnings never repeat reads. */
|
|
5
|
+
export declare function usePlacementLabels({ details, preflight, apiRoute, adminRoute, canManage, collectionTitles }: {
|
|
6
|
+
details: unknown[];
|
|
7
|
+
preflight?: PreflightResult | null;
|
|
8
|
+
apiRoute: string;
|
|
9
|
+
adminRoute: string;
|
|
10
|
+
canManage: boolean;
|
|
11
|
+
collectionTitles?: Record<string, string>;
|
|
12
|
+
}): Map<string, PlacementLabel>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { createApi } from '../api.js';
|
|
4
|
+
import { placementDetail, placementDetailKey, preflightPlacementLabel, resolvedPlacementLabel } from './placementLabels.js';
|
|
5
|
+
export { placementDetailKey } from './placementLabels.js';
|
|
6
|
+
/** The view owns a cache, including misses, so repeated warnings never repeat reads. */ export function usePlacementLabels({ details, preflight, apiRoute, adminRoute, canManage, collectionTitles }) {
|
|
7
|
+
const titlesSignature = JSON.stringify(collectionTitles);
|
|
8
|
+
const cache = useMemo(()=>new Map(), [
|
|
9
|
+
apiRoute,
|
|
10
|
+
adminRoute,
|
|
11
|
+
titlesSignature
|
|
12
|
+
]);
|
|
13
|
+
const mounted = useRef(true);
|
|
14
|
+
useEffect(()=>{
|
|
15
|
+
mounted.current = true;
|
|
16
|
+
return ()=>{
|
|
17
|
+
mounted.current = false;
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
const [, refresh] = useState(0);
|
|
21
|
+
const signature = JSON.stringify(details.map(placementDetail));
|
|
22
|
+
useEffect(()=>{
|
|
23
|
+
if (!canManage || preflight) return;
|
|
24
|
+
const pending = [
|
|
25
|
+
...new Map(JSON.parse(signature).map((detail)=>[
|
|
26
|
+
placementDetailKey(detail),
|
|
27
|
+
detail
|
|
28
|
+
])).values()
|
|
29
|
+
].filter((detail)=>!cache.has(placementDetailKey(detail)));
|
|
30
|
+
const blocks = new Map();
|
|
31
|
+
const jobs = [];
|
|
32
|
+
const save = (value, label)=>{
|
|
33
|
+
cache.set(placementDetailKey(value), label);
|
|
34
|
+
};
|
|
35
|
+
for (const value of pending){
|
|
36
|
+
const detail = placementDetail(value);
|
|
37
|
+
// Reserve immediately: repeated renders do not issue duplicate requests.
|
|
38
|
+
save(value, resolvedPlacementLabel(value, undefined, adminRoute));
|
|
39
|
+
if (!detail.collection) continue;
|
|
40
|
+
if (detail.id != null) {
|
|
41
|
+
blocks.set(detail.collection, [
|
|
42
|
+
...blocks.get(detail.collection) ?? [],
|
|
43
|
+
value
|
|
44
|
+
]);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!detail.path) continue;
|
|
48
|
+
jobs.push(createApi(apiRoute).pages({
|
|
49
|
+
collection: detail.collection,
|
|
50
|
+
path: detail.path
|
|
51
|
+
}).then((result)=>{
|
|
52
|
+
if (result.ok) save(value, resolvedPlacementLabel(value, result.data?.docs[0], adminRoute));
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
for (const [collection, values] of blocks)jobs.push((async ()=>{
|
|
56
|
+
try {
|
|
57
|
+
const ids = [
|
|
58
|
+
...new Set(values.map((value)=>String(placementDetail(value).id)))
|
|
59
|
+
];
|
|
60
|
+
const query = new URLSearchParams({
|
|
61
|
+
'where[id][in]': ids.join(','),
|
|
62
|
+
depth: '0',
|
|
63
|
+
limit: String(ids.length)
|
|
64
|
+
});
|
|
65
|
+
const response = await fetch(`${apiRoute.replace(/\/$/, '')}/${encodeURIComponent(collection)}?${query}`, {
|
|
66
|
+
credentials: 'include'
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) return;
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
const titleField = collectionTitles?.[collection];
|
|
71
|
+
for (const value of values){
|
|
72
|
+
const doc = data.docs?.find((doc)=>String(doc.id) === String(placementDetail(value).id));
|
|
73
|
+
if (doc) save(value, resolvedPlacementLabel(value, {
|
|
74
|
+
id: placementDetail(value).id,
|
|
75
|
+
title: titleField && typeof doc[titleField] === 'string' ? doc[titleField] : undefined
|
|
76
|
+
}, adminRoute));
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
})());
|
|
80
|
+
void Promise.all(jobs).then(()=>{
|
|
81
|
+
if (mounted.current && jobs.length) refresh((value)=>value + 1);
|
|
82
|
+
});
|
|
83
|
+
}, [
|
|
84
|
+
signature,
|
|
85
|
+
preflight,
|
|
86
|
+
apiRoute,
|
|
87
|
+
adminRoute,
|
|
88
|
+
canManage,
|
|
89
|
+
collectionTitles,
|
|
90
|
+
cache
|
|
91
|
+
]);
|
|
92
|
+
return new Map(details.map((detail)=>{
|
|
93
|
+
const label = preflight ? preflightPlacementLabel(detail, preflight, adminRoute) : cache.get(placementDetailKey(detail)) ?? resolvedPlacementLabel(detail, undefined, adminRoute);
|
|
94
|
+
// Results-only readers may not be able to open the collection, so they get the name without a link.
|
|
95
|
+
return [
|
|
96
|
+
placementDetailKey(detail),
|
|
97
|
+
canManage ? label : {
|
|
98
|
+
title: label.title
|
|
99
|
+
}
|
|
100
|
+
];
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
@@ -41,7 +41,10 @@ export function DataQualityPanel({ items, problem = false }) {
|
|
|
41
41
|
}) : null,
|
|
42
42
|
/*#__PURE__*/ _jsx("p", {
|
|
43
43
|
className: "xp-quality-next",
|
|
44
|
-
children: item.
|
|
44
|
+
children: item.href ? /*#__PURE__*/ _jsx("a", {
|
|
45
|
+
href: item.href,
|
|
46
|
+
children: item.nextStep
|
|
47
|
+
}) : item.nextStep
|
|
45
48
|
})
|
|
46
49
|
]
|
|
47
50
|
}, item.id)) : /*#__PURE__*/ _jsx("p", {
|
|
@@ -2,6 +2,7 @@ import type { DocId, Status } from '../../types/contracts.js';
|
|
|
2
2
|
import type { PhaseSummary, ResultsResponse } from '../../types/endpoints.js';
|
|
3
3
|
import type { Snapshot } from '../../types/snapshot.js';
|
|
4
4
|
import { type DisplaySnapshot } from './snapshot.js';
|
|
5
|
+
import type { PlacementLabel } from '../placements/placementLabels.model.js';
|
|
5
6
|
type Metric = Snapshot['metrics'][number];
|
|
6
7
|
type Arm = Metric['perArm'][number];
|
|
7
8
|
type Decision = {
|
|
@@ -88,6 +89,7 @@ interface RoundLineInput {
|
|
|
88
89
|
}
|
|
89
90
|
export declare function powerLine({ snapshot, phaseKind, status }: RoundLineInput): string | null;
|
|
90
91
|
export interface ResultsContext {
|
|
92
|
+
placementLabels?: Map<string, PlacementLabel>;
|
|
91
93
|
status?: string;
|
|
92
94
|
phase?: PhaseSummary;
|
|
93
95
|
minExposuresPerArm?: number;
|
|
@@ -102,6 +104,7 @@ export interface DataQualityItem {
|
|
|
102
104
|
message: string;
|
|
103
105
|
detail?: string;
|
|
104
106
|
nextStep: string;
|
|
107
|
+
href?: string;
|
|
105
108
|
shares?: string[];
|
|
106
109
|
}
|
|
107
110
|
export declare function dataQuality(snapshot: DisplaySnapshot, ctx?: ResultsContext): DataQualityItem[];
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { READINESS_COPY, versionPageUnavailableCopy, versionPageUncheckedCopy } from '../copy/readiness.js';
|
|
1
|
+
import { READINESS_COPY, placementUncheckedCopy, versionPageUnavailableCopy, versionPageUncheckedCopy } from '../copy/readiness.js';
|
|
2
2
|
import { armLabel, phaseKindLabel, verdictCodeLabel } from '../labels.js';
|
|
3
3
|
import { RESULTS_COPY, cappedCount, collectingCopy, conflictCopy, decidedTitle, exclusionCopy, ignoredCopy, metricUnavailable, moreDays, roundLabel, shareCopy, shippingTitle, srmP, uncappedTotal } from '../copy/results.js';
|
|
4
4
|
import { formatCI, formatCount, formatMetric, formatPercent, formatProbability } from './format.js';
|
|
5
5
|
import { legacyMinimum, legacyRolloutLine, legacyTooFew } from './legacy.js';
|
|
6
6
|
import { isLegacySnapshot } from './snapshot.js';
|
|
7
|
+
import { placementDetailKey } from '../placements/placementLabels.model.js';
|
|
7
8
|
export function defaultRound({ phases, decision, status }) {
|
|
8
9
|
if ((status === 'decided' || status === 'archived') && decision) return decision.phase;
|
|
9
10
|
return phases.reduce((latest, phase)=>latest === null || phase.phase > latest ? phase.phase : latest, null);
|
|
@@ -132,9 +133,12 @@ function smallest(values) {
|
|
|
132
133
|
}
|
|
133
134
|
export function dataQuality(snapshot, ctx = {}) {
|
|
134
135
|
const items = (snapshot.health ?? []).map((item)=>({
|
|
135
|
-
id: `${item.code}:${item.path}`,
|
|
136
|
-
message: READINESS_COPY.needsAttention,
|
|
137
|
-
|
|
136
|
+
id: item.code === 'placement-unchecked' ? `${item.code}:${JSON.stringify(item.detail)}` : `${item.code}:${'path' in item ? item.path : item.metricId}`,
|
|
137
|
+
message: item.code === 'placement-unchecked' ? placementUncheckedCopy(ctx.placementLabels?.get(placementDetailKey(item.detail))?.title ?? item.detail.path ?? 'This page') : item.code === 'goal-in-some-versions' ? `${item.name} now appears in some versions but not all, so this result is no longer a fair comparison. Add it to every version, or remove it.` : READINESS_COPY.needsAttention,
|
|
138
|
+
...item.code === 'placement-unchecked' ? {
|
|
139
|
+
href: ctx.placementLabels?.get(placementDetailKey(item.detail))?.href
|
|
140
|
+
} : {},
|
|
141
|
+
nextStep: item.code === 'placement-unchecked' ? `Open ${ctx.placementLabels?.get(placementDetailKey(item.detail))?.title ?? item.detail.path ?? 'the page'}` : item.code === 'goal-in-some-versions' ? 'Edit the versions' : item.code === 'version-page-unavailable' ? versionPageUnavailableCopy(item.path, ctx.status) : versionPageUncheckedCopy(item.path, ctx.status)
|
|
138
142
|
}));
|
|
139
143
|
// Test snapshots store null by design; only rollout snapshots record the shipped winner.
|
|
140
144
|
if (!isLegacySnapshot(snapshot) && snapshot.phaseKind === 'rollout' && snapshot.winnerArm === null) items.push({
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
-
import { Link } from '@payloadcms/ui';
|
|
5
4
|
import { formatAdminURL } from 'payload/shared';
|
|
6
5
|
import { fetchAllDocs } from '../../lib/fetchAllDocs.js';
|
|
7
6
|
import { createApi } from '../api.js';
|
|
@@ -10,6 +9,7 @@ import { Banner, EmptyState, StatusChip } from '../../ui/Feedback.js';
|
|
|
10
9
|
import { Button } from '../../ui/Button.js';
|
|
11
10
|
import { SearchInput } from '../../ui/Inputs.js';
|
|
12
11
|
import { SurfaceShell } from './SurfaceShell.js';
|
|
12
|
+
import { sanitizeReadinessDetail } from '../../sanitizeReadinessDetail.js';
|
|
13
13
|
import { groupTests, homeCard } from './home.model.js';
|
|
14
14
|
const HOME_FIELDS = [
|
|
15
15
|
'name',
|
|
@@ -52,11 +52,40 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
52
52
|
const docs = response.docs.sort((a, b)=>(Date.parse(b.updatedAt ?? '') || 0) - (Date.parse(a.updatedAt ?? '') || 0));
|
|
53
53
|
if (!active) return;
|
|
54
54
|
const summary = await api.summary(docs.map((doc)=>doc.id));
|
|
55
|
+
const summaries = summary.ok ? summary.data ?? {} : {};
|
|
56
|
+
const ids = [
|
|
57
|
+
...new Set(docs.flatMap((doc)=>{
|
|
58
|
+
const item = summaries[doc.id];
|
|
59
|
+
if (!canManage || ![
|
|
60
|
+
'draft',
|
|
61
|
+
'stopped'
|
|
62
|
+
].includes(doc.status) || item?.ready === true || item?.next?.code !== 'goal-in-some-versions') return [];
|
|
63
|
+
const detail = sanitizeReadinessDetail(item.next.code, item.next.detail);
|
|
64
|
+
return detail?.map((row)=>String(row.metricId)) ?? [];
|
|
65
|
+
}))
|
|
66
|
+
];
|
|
67
|
+
const goalNames = {};
|
|
68
|
+
if (ids.length) {
|
|
69
|
+
try {
|
|
70
|
+
const query = new URLSearchParams({
|
|
71
|
+
'where[id][in]': ids.join(','),
|
|
72
|
+
depth: '0',
|
|
73
|
+
limit: String(ids.length)
|
|
74
|
+
});
|
|
75
|
+
const response = await fetch(`${apiRoute.replace(/\/$/, '')}/xp-metrics?${query}`, {
|
|
76
|
+
credentials: 'include',
|
|
77
|
+
signal: controller.signal
|
|
78
|
+
});
|
|
79
|
+
const body = response.ok ? await response.json() : undefined;
|
|
80
|
+
for (const goal of body?.docs ?? [])if (typeof goal.name === 'string' && goal.name.trim() && ids.includes(String(goal.id))) goalNames[String(goal.id)] = goal.name;
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
55
83
|
if (active) setState({
|
|
56
84
|
loading: false,
|
|
57
85
|
error: false,
|
|
58
86
|
tests: docs,
|
|
59
|
-
|
|
87
|
+
goalNames,
|
|
88
|
+
summaries,
|
|
60
89
|
summaryError: !summary.ok
|
|
61
90
|
});
|
|
62
91
|
} catch {
|
|
@@ -74,7 +103,8 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
74
103
|
}, [
|
|
75
104
|
api,
|
|
76
105
|
apiRoute,
|
|
77
|
-
attempt
|
|
106
|
+
attempt,
|
|
107
|
+
canManage
|
|
78
108
|
]);
|
|
79
109
|
const retry = /*#__PURE__*/ _jsx(Button, {
|
|
80
110
|
size: "sm",
|
|
@@ -153,6 +183,7 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
153
183
|
}) : /*#__PURE__*/ _jsx(TestGroups, {
|
|
154
184
|
tests: state.tests,
|
|
155
185
|
summaries: state.summaries,
|
|
186
|
+
goalNames: state.goalNames,
|
|
156
187
|
adminRoute: adminRoute,
|
|
157
188
|
flagPoints: flagPoints,
|
|
158
189
|
search: search,
|
|
@@ -162,7 +193,7 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
162
193
|
})
|
|
163
194
|
});
|
|
164
195
|
}
|
|
165
|
-
function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear }) {
|
|
196
|
+
function TestGroups({ tests, summaries, goalNames, adminRoute, flagPoints, search, clear }) {
|
|
166
197
|
const groups = groupTests(tests, summaries, search);
|
|
167
198
|
if (!groups.some((group)=>group.tests.length)) return /*#__PURE__*/ _jsxs("div", {
|
|
168
199
|
className: "xp-tests-no-matches",
|
|
@@ -184,7 +215,7 @@ function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear })
|
|
|
184
215
|
const rows = /*#__PURE__*/ _jsx("div", {
|
|
185
216
|
className: "xp-tests-list",
|
|
186
217
|
children: group.tests.map((test)=>{
|
|
187
|
-
const card = homeCard(test, summaries[test.id], adminRoute, flagPoints);
|
|
218
|
+
const card = homeCard(test, summaries[test.id], adminRoute, flagPoints, goalNames);
|
|
188
219
|
return /*#__PURE__*/ _jsxs("article", {
|
|
189
220
|
className: "xp-tests-row",
|
|
190
221
|
children: [
|
|
@@ -194,7 +225,7 @@ function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear })
|
|
|
194
225
|
/*#__PURE__*/ _jsxs("div", {
|
|
195
226
|
className: "xp-tests-row-name",
|
|
196
227
|
children: [
|
|
197
|
-
/*#__PURE__*/ _jsx(
|
|
228
|
+
/*#__PURE__*/ _jsx("a", {
|
|
198
229
|
href: card.href,
|
|
199
230
|
children: test.name
|
|
200
231
|
}),
|
|
@@ -28,7 +28,11 @@ function SurfacePage(props) {
|
|
|
28
28
|
flagPoints: props.flagPoints,
|
|
29
29
|
attributeKeys: props.attributeKeys,
|
|
30
30
|
metricSources: props.metricSources,
|
|
31
|
-
|
|
31
|
+
admin: props.admin,
|
|
32
|
+
pageCollections: props.pageCollections,
|
|
33
|
+
collectionTitles: props.collectionTitles,
|
|
34
|
+
userId: props.userId,
|
|
35
|
+
userCollection: props.userCollection
|
|
32
36
|
};
|
|
33
37
|
const crumbs = [
|
|
34
38
|
{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentType } from 'react';
|
|
2
|
+
import type { TestsSurfaceProps } from '../../types/surface.js';
|
|
2
3
|
import type { SerializedAttributeKeys, SerializedFlagPoint, SerializedMetricSource } from '../../types/admin.js';
|
|
3
4
|
import type { useTestDocument } from './useTestDocument.js';
|
|
4
5
|
/** Sections of the test page, in readiness order (surface spec §6.2, §8). */
|
|
@@ -10,17 +11,17 @@ export type TestDocApi = ReturnType<typeof useTestDocument>;
|
|
|
10
11
|
export interface SurfaceProps {
|
|
11
12
|
adminRoute: string;
|
|
12
13
|
apiRoute: string;
|
|
14
|
+
collectionTitles?: Record<string, string>;
|
|
15
|
+
userId?: string | number;
|
|
16
|
+
userCollection?: string;
|
|
13
17
|
canManage: boolean;
|
|
14
18
|
canReadResults: boolean;
|
|
15
19
|
isAdmin: boolean;
|
|
16
20
|
flagPoints: SerializedFlagPoint[];
|
|
17
21
|
attributeKeys: SerializedAttributeKeys;
|
|
18
22
|
metricSources: SerializedMetricSource[];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
document?: string;
|
|
22
|
-
setting?: string;
|
|
23
|
-
};
|
|
23
|
+
pageCollections?: TestsSurfaceProps['pageCollections'];
|
|
24
|
+
admin?: TestsSurfaceProps['admin'];
|
|
24
25
|
}
|
|
25
26
|
export interface SectionProps {
|
|
26
27
|
test: TestDocApi;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
5
|
import { formatAdminURL } from 'payload/shared';
|
|
6
6
|
import { GOALS_COPY as C, goalDeleteLocked, goalInUse } from '../../copy/goals.js';
|
|
7
|
-
import { metricSourceHint, metricSourceOptions } from '../../metricSource.model.js';
|
|
7
|
+
import { metricSourceHint, metricSourceOptions, sourceFor } from '../../metricSource.model.js';
|
|
8
8
|
import { validateCappingType, validateCappingValue } from '../../../collections/metricValidation.js';
|
|
9
|
+
import { CLICK_KEY } from '../../../goals/key.js';
|
|
10
|
+
import { clickGoalSlug } from '../../../goals/picker/clickGoalPicker.model.js';
|
|
9
11
|
import { goalSlug, goalUsage } from './goals.model.js';
|
|
10
12
|
import { GoalResponseError, goalPages, goalResponse } from './data.js';
|
|
11
13
|
import { SURFACE_COPY } from '../../copy/surface.js';
|
|
@@ -55,7 +57,7 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
55
57
|
const advanced = useRef(null);
|
|
56
58
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
57
59
|
useEffect(()=>{
|
|
58
|
-
if (advanced.current && (fieldErrors.capType || fieldErrors.capValue || fieldErrors.warningPercent)) advanced.current.open = true;
|
|
60
|
+
if (advanced.current && (fieldErrors.slug || fieldErrors.capType || fieldErrors.capValue || fieldErrors.warningPercent)) advanced.current.open = true;
|
|
59
61
|
}, [
|
|
60
62
|
fieldErrors
|
|
61
63
|
]);
|
|
@@ -84,7 +86,7 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
84
86
|
]);
|
|
85
87
|
if (loaded.id == null) throw new Error(C.missing);
|
|
86
88
|
if (controller.signal.aborted) return;
|
|
87
|
-
const defaultCap =
|
|
89
|
+
const defaultCap = sourceFor(loaded.source, surface.metricSources)?.kind === 'revenue' && loaded.capping?.type == null && loaded.capping?.value == null;
|
|
88
90
|
setGoal(loaded);
|
|
89
91
|
setUsedBy(goalUsage(tests).get(loaded.id) ?? 0);
|
|
90
92
|
setDraft({
|
|
@@ -107,7 +109,8 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
107
109
|
id,
|
|
108
110
|
attempt
|
|
109
111
|
]);
|
|
110
|
-
const source =
|
|
112
|
+
const source = sourceFor(draft.source, surface.metricSources);
|
|
113
|
+
const click = draft.source.startsWith('click:');
|
|
111
114
|
const hint = metricSourceHint(source);
|
|
112
115
|
const options = metricSourceOptions(surface.metricSources);
|
|
113
116
|
const set = (key, value)=>setDraft((current)=>({
|
|
@@ -116,11 +119,14 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
116
119
|
}));
|
|
117
120
|
async function submit() {
|
|
118
121
|
if (inFlight.current || !ready || !surface.canManage) return;
|
|
119
|
-
const capValue = draft.capValue.trim() === '' ? null : Number(draft.capValue);
|
|
120
|
-
const typeError = validateCappingType(draft.capType || null, capValue, source?.kind);
|
|
121
|
-
const valueError = validateCappingValue(capValue, draft.capType || null);
|
|
122
|
+
const capValue = click || draft.capValue.trim() === '' ? null : Number(draft.capValue);
|
|
123
|
+
const typeError = validateCappingType(click ? null : draft.capType || null, capValue, source?.kind);
|
|
124
|
+
const valueError = validateCappingValue(capValue, click ? null : draft.capType || null);
|
|
122
125
|
const warningPercent = draft.warningPercent.trim() === '' ? 5 : Number(draft.warningPercent);
|
|
123
126
|
const errors = {
|
|
127
|
+
...click && !CLICK_KEY.test(draft.slug) ? {
|
|
128
|
+
slug: C.slugRule
|
|
129
|
+
} : {},
|
|
124
130
|
...typeError !== true ? {
|
|
125
131
|
capType: typeError
|
|
126
132
|
} : {},
|
|
@@ -141,12 +147,12 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
141
147
|
const body = {
|
|
142
148
|
name: draft.name,
|
|
143
149
|
slug: usedBy && goal ? goal.slug : draft.slug,
|
|
144
|
-
source: usedBy && goal ? goal.source : draft.source,
|
|
145
|
-
direction: draft.direction,
|
|
150
|
+
source: usedBy && goal ? goal.source : click ? `click:${draft.slug}` : draft.source,
|
|
151
|
+
direction: click ? 'increase' : draft.direction,
|
|
146
152
|
attributionWindowHours: draft.attributionWindowHours,
|
|
147
153
|
minSampleSize: draft.minSampleSize,
|
|
148
154
|
capping: {
|
|
149
|
-
type: draft.capType || null,
|
|
155
|
+
type: click ? null : draft.capType || null,
|
|
150
156
|
value: capValue
|
|
151
157
|
},
|
|
152
158
|
guardrailThreshold: warningPercent / 100
|
|
@@ -170,11 +176,11 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
170
176
|
})}`
|
|
171
177
|
}) : listUrl);
|
|
172
178
|
} catch (cause) {
|
|
173
|
-
setError(cause instanceof
|
|
179
|
+
setError(cause instanceof GoalResponseError ? cause.message : C.saveError);
|
|
174
180
|
if (cause instanceof GoalResponseError) setFieldErrors(Object.fromEntries(Object.entries(cause.fields).flatMap(([path, message])=>fieldNames[path] ? [
|
|
175
181
|
[
|
|
176
182
|
fieldNames[path],
|
|
177
|
-
message
|
|
183
|
+
path === 'slug' ? C.duplicateSlug : message
|
|
178
184
|
]
|
|
179
185
|
] : [])));
|
|
180
186
|
inFlight.current = false;
|
|
@@ -272,41 +278,54 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
272
278
|
...current,
|
|
273
279
|
name,
|
|
274
280
|
...!slugEdited && !usedBy ? {
|
|
275
|
-
slug: goalSlug(name)
|
|
281
|
+
slug: click ? clickGoalSlug(name) : goalSlug(name)
|
|
276
282
|
} : {}
|
|
277
283
|
}));
|
|
284
|
+
if (click && !slugEdited && !usedBy) setFieldErrors((current)=>({
|
|
285
|
+
...current,
|
|
286
|
+
slug: clickGoalSlug(name) ? undefined : C.slugRule
|
|
287
|
+
}));
|
|
278
288
|
}
|
|
279
289
|
}),
|
|
280
|
-
/*#__PURE__*/ _jsx(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
290
|
+
!click ? /*#__PURE__*/ _jsx(_Fragment, {
|
|
291
|
+
children: /*#__PURE__*/ _jsx(TextInput, {
|
|
292
|
+
id: "xp-goal-slug",
|
|
293
|
+
label: C.slug,
|
|
294
|
+
required: true,
|
|
295
|
+
value: draft.slug,
|
|
296
|
+
readOnly: usedBy > 0,
|
|
297
|
+
help: usedBy ? C.slugLocked : C.slugHelp,
|
|
298
|
+
error: fieldErrors.slug,
|
|
299
|
+
onChange: (event)=>{
|
|
300
|
+
setSlugEdited(true);
|
|
301
|
+
set('slug', event.currentTarget.value);
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
}) : null,
|
|
293
305
|
/*#__PURE__*/ _jsxs(Select, {
|
|
294
306
|
id: "xp-goal-source",
|
|
295
307
|
label: C.source,
|
|
296
308
|
required: true,
|
|
297
|
-
value: draft.source,
|
|
309
|
+
value: click ? 'click:' : draft.source,
|
|
298
310
|
disabled: usedBy > 0,
|
|
299
311
|
help: usedBy ? C.sourceLocked : hint || undefined,
|
|
300
312
|
error: fieldErrors.source,
|
|
301
313
|
onChange: (event)=>{
|
|
302
314
|
const sourceId = event.currentTarget.value;
|
|
303
|
-
const revenue = surface.metricSources
|
|
315
|
+
const revenue = sourceFor(sourceId, surface.metricSources)?.kind === 'revenue';
|
|
304
316
|
setDraft((current)=>({
|
|
305
317
|
...current,
|
|
306
318
|
source: sourceId,
|
|
319
|
+
...!slugEdited && !usedBy ? {
|
|
320
|
+
slug: sourceId === 'click:' ? clickGoalSlug(current.name) : goalSlug(current.name)
|
|
321
|
+
} : {},
|
|
307
322
|
capType: revenue ? 'percentile' : '',
|
|
308
323
|
capValue: revenue ? '0.99' : ''
|
|
309
324
|
}));
|
|
325
|
+
if (sourceId === 'click:' && !slugEdited && !usedBy) setFieldErrors((current)=>({
|
|
326
|
+
...current,
|
|
327
|
+
slug: clickGoalSlug(draft.name) ? undefined : C.slugRule
|
|
328
|
+
}));
|
|
310
329
|
},
|
|
311
330
|
children: [
|
|
312
331
|
/*#__PURE__*/ _jsx("option", {
|
|
@@ -315,15 +334,15 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
315
334
|
}),
|
|
316
335
|
options.map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
317
336
|
value: option.value,
|
|
318
|
-
children: option.label
|
|
337
|
+
children: option.value === 'click:' ? sourceFor(option.value, surface.metricSources)?.label : option.label
|
|
319
338
|
}, option.value)),
|
|
320
|
-
draft.source && !options.some((option)=>option.value === draft.source) ? /*#__PURE__*/ _jsx("option", {
|
|
339
|
+
!click && draft.source && !options.some((option)=>option.value === draft.source) ? /*#__PURE__*/ _jsx("option", {
|
|
321
340
|
value: draft.source,
|
|
322
341
|
children: draft.source
|
|
323
342
|
}) : null
|
|
324
343
|
]
|
|
325
344
|
}),
|
|
326
|
-
/*#__PURE__*/ _jsxs(Select, {
|
|
345
|
+
!click ? /*#__PURE__*/ _jsxs(Select, {
|
|
327
346
|
id: "xp-goal-direction",
|
|
328
347
|
label: C.direction,
|
|
329
348
|
value: draft.direction,
|
|
@@ -339,7 +358,7 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
339
358
|
children: C.decrease
|
|
340
359
|
})
|
|
341
360
|
]
|
|
342
|
-
}),
|
|
361
|
+
}) : null,
|
|
343
362
|
/*#__PURE__*/ _jsx(NumberInput, {
|
|
344
363
|
id: "xp-goal-window",
|
|
345
364
|
label: C.window,
|
|
@@ -370,7 +389,21 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
370
389
|
/*#__PURE__*/ _jsxs("div", {
|
|
371
390
|
className: "xp-goal-advanced-fields",
|
|
372
391
|
children: [
|
|
373
|
-
/*#__PURE__*/
|
|
392
|
+
click ? /*#__PURE__*/ _jsx(_Fragment, {
|
|
393
|
+
children: /*#__PURE__*/ _jsx(TextInput, {
|
|
394
|
+
id: "xp-goal-slug",
|
|
395
|
+
label: C.slug,
|
|
396
|
+
required: true,
|
|
397
|
+
value: draft.slug,
|
|
398
|
+
readOnly: usedBy > 0,
|
|
399
|
+
help: usedBy ? C.slugLocked : C.slugHelp,
|
|
400
|
+
error: fieldErrors.slug,
|
|
401
|
+
onChange: (event)=>{
|
|
402
|
+
setSlugEdited(true);
|
|
403
|
+
set('slug', event.currentTarget.value);
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
}) : /*#__PURE__*/ _jsxs("fieldset", {
|
|
374
407
|
children: [
|
|
375
408
|
/*#__PURE__*/ _jsx("legend", {
|
|
376
409
|
children: C.capping
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { sourceFor } from '../../metricSource.model.js';
|
|
3
4
|
import { useEffect, useState } from 'react';
|
|
4
|
-
import { Link } from '@payloadcms/ui';
|
|
5
5
|
import { formatAdminURL } from 'payload/shared';
|
|
6
6
|
import { Banner, EmptyState } from '../../../ui/Feedback.js';
|
|
7
7
|
import { Button } from '../../../ui/Button.js';
|
|
@@ -111,7 +111,7 @@ export function GoalsList({ surface, returnToTest }) {
|
|
|
111
111
|
className: "xp-tests-list",
|
|
112
112
|
children: goals.map((goal)=>{
|
|
113
113
|
const count = usage.get(goal.id) ?? 0;
|
|
114
|
-
const kind =
|
|
114
|
+
const kind = sourceFor(goal.source, surface.metricSources)?.kind;
|
|
115
115
|
const label = kind && Object.hasOwn(METRIC_KIND_LABELS, kind) ? METRIC_KIND_LABELS[kind] : undefined;
|
|
116
116
|
const href = url(`/experiments/goals/${encodeURIComponent(String(goal.id))}${returnQuery}`);
|
|
117
117
|
return /*#__PURE__*/ _jsxs("article", {
|
|
@@ -123,7 +123,7 @@ export function GoalsList({ surface, returnToTest }) {
|
|
|
123
123
|
/*#__PURE__*/ _jsxs("div", {
|
|
124
124
|
className: "xp-tests-row-name",
|
|
125
125
|
children: [
|
|
126
|
-
/*#__PURE__*/ _jsx(
|
|
126
|
+
/*#__PURE__*/ _jsx("a", {
|
|
127
127
|
href: href,
|
|
128
128
|
children: goal.name
|
|
129
129
|
}),
|
|
@@ -17,7 +17,10 @@ export class GoalResponseError extends Error {
|
|
|
17
17
|
function goalRefusal(body, status, fallback, options) {
|
|
18
18
|
const fields = payloadFieldErrors(body, '_section', fallback);
|
|
19
19
|
const first = body?.errors?.[0];
|
|
20
|
-
throw new GoalResponseError(refusalMessage(first?.code, status,
|
|
20
|
+
throw new GoalResponseError(refusalMessage(first?.code, status, undefined, {
|
|
21
|
+
...options,
|
|
22
|
+
fallback
|
|
23
|
+
}), fields);
|
|
21
24
|
}
|
|
22
25
|
export async function goalPages(base, collection, signal) {
|
|
23
26
|
const result = await fetchAllDocs(`${base}/${collection}?depth=0`, {
|
|
@@ -26,5 +26,4 @@ export interface GoalUsageTest {
|
|
|
26
26
|
}
|
|
27
27
|
/** Count each experiment once per goal, retaining the API's id types. */
|
|
28
28
|
export declare function goalUsage(tests: readonly GoalUsageTest[]): Map<DocId, number>;
|
|
29
|
-
export
|
|
30
|
-
export {};
|
|
29
|
+
export { goalSlug } from '../../../goals/picker/clickGoalPicker.model.js';
|
|
@@ -21,6 +21,4 @@
|
|
|
21
21
|
tests.size
|
|
22
22
|
]));
|
|
23
23
|
}
|
|
24
|
-
export
|
|
25
|
-
return name.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
26
|
-
}
|
|
24
|
+
export { goalSlug } from '../../../goals/picker/clickGoalPicker.model.js';
|
|
@@ -26,7 +26,7 @@ export declare function groupTests(tests: readonly HomeTest[], summaries: Summar
|
|
|
26
26
|
label: "Needs attention" | "Running" | "Decided" | "Draft" | "Archived" | "Ready to start";
|
|
27
27
|
tests: HomeTest[];
|
|
28
28
|
}[];
|
|
29
|
-
export declare function homeCard(test: HomeTest, summary: Summary, adminRoute: string, flagPoints?: ReadinessContext['flagPoints']): {
|
|
29
|
+
export declare function homeCard(test: HomeTest, summary: Summary, adminRoute: string, flagPoints?: ReadinessContext['flagPoints'], goalNames?: Readonly<Record<string, string>>): {
|
|
30
30
|
kind: "Whole page" | "Page block" | "Setting";
|
|
31
31
|
pill: {
|
|
32
32
|
label: string;
|