@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,168 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Button, FieldError, FieldLabel, SelectInput, TextInput, useAuth, useConfig, useField } from '@payloadcms/ui';
|
|
4
|
+
import { useId } from 'react';
|
|
5
|
+
import { useClickGoalPicker } from '../../goals/picker/useClickGoalPicker.js';
|
|
6
|
+
import { CLICK_GOAL_COPY as C } from '../../goals/picker/copy.js';
|
|
7
|
+
import '../theme.css';
|
|
8
|
+
import './xpGoalField.css';
|
|
9
|
+
export function XpGoalField({ path, field, readOnly }) {
|
|
10
|
+
const { value, setValue, showError } = useField({
|
|
11
|
+
path
|
|
12
|
+
});
|
|
13
|
+
const { permissions } = useAuth();
|
|
14
|
+
const { config } = useConfig();
|
|
15
|
+
const access = permissions?.collections?.['xp-experiments'];
|
|
16
|
+
const locked = Boolean(readOnly || field.admin?.readOnly);
|
|
17
|
+
const picker = useClickGoalPicker(config.routes.api, {
|
|
18
|
+
enabled: access?.update === true,
|
|
19
|
+
onSelect: setValue
|
|
20
|
+
});
|
|
21
|
+
const id = useId(), namePath = `${id}-name`, slugPath = `${id}-slug`;
|
|
22
|
+
const stopEnter = (event)=>{
|
|
23
|
+
if (event.key === 'Enter') {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
event.stopPropagation();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
29
|
+
className: "field-type xp-goal-field",
|
|
30
|
+
children: [
|
|
31
|
+
/*#__PURE__*/ _jsx(TextInput, {
|
|
32
|
+
path: path,
|
|
33
|
+
label: field.label ?? C.label,
|
|
34
|
+
description: field.admin?.description,
|
|
35
|
+
required: field.required,
|
|
36
|
+
readOnly: locked,
|
|
37
|
+
value: value ?? '',
|
|
38
|
+
showError: showError,
|
|
39
|
+
Error: /*#__PURE__*/ _jsx(FieldError, {
|
|
40
|
+
path: path,
|
|
41
|
+
showError: showError
|
|
42
|
+
}),
|
|
43
|
+
onChange: (event)=>{
|
|
44
|
+
if (!locked) setValue(event.currentTarget.value);
|
|
45
|
+
}
|
|
46
|
+
}),
|
|
47
|
+
picker.canManage && !locked ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
48
|
+
children: [
|
|
49
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
50
|
+
className: "xp-goal-field__row",
|
|
51
|
+
children: [
|
|
52
|
+
/*#__PURE__*/ _jsx(SelectInput, {
|
|
53
|
+
name: `${path}__xpGoalPicker`,
|
|
54
|
+
path: `${path}__xpGoalPicker`,
|
|
55
|
+
className: "xp-goal-field__select",
|
|
56
|
+
value: picker.goals.some((goal)=>goal.slug === value) ? value : undefined,
|
|
57
|
+
options: picker.goals.map((goal)=>({
|
|
58
|
+
value: goal.slug,
|
|
59
|
+
label: goal.name
|
|
60
|
+
})),
|
|
61
|
+
placeholder: C.choose,
|
|
62
|
+
isClearable: false,
|
|
63
|
+
label: C.choose,
|
|
64
|
+
onChange: (option)=>{
|
|
65
|
+
if (option && !Array.isArray(option)) setValue(String(option.value));
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
68
|
+
!picker.panel.open ? /*#__PURE__*/ _jsx(Button, {
|
|
69
|
+
type: "button",
|
|
70
|
+
margin: false,
|
|
71
|
+
buttonStyle: "secondary",
|
|
72
|
+
onClick: picker.open,
|
|
73
|
+
children: C.newGoal
|
|
74
|
+
}) : null
|
|
75
|
+
]
|
|
76
|
+
}),
|
|
77
|
+
picker.undefinedKey(value ?? '') ? /*#__PURE__*/ _jsx("p", {
|
|
78
|
+
role: "status",
|
|
79
|
+
children: C.undefinedKey
|
|
80
|
+
}) : null,
|
|
81
|
+
picker.panel.open ? /*#__PURE__*/ _jsxs("section", {
|
|
82
|
+
className: "xp-goal-field__panel",
|
|
83
|
+
"aria-labelledby": `${id}-title`,
|
|
84
|
+
children: [
|
|
85
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
86
|
+
id: `${id}-title`,
|
|
87
|
+
children: C.title
|
|
88
|
+
}),
|
|
89
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
90
|
+
className: "field-type text",
|
|
91
|
+
children: [
|
|
92
|
+
/*#__PURE__*/ _jsx(FieldLabel, {
|
|
93
|
+
path: namePath,
|
|
94
|
+
label: C.name
|
|
95
|
+
}),
|
|
96
|
+
/*#__PURE__*/ _jsx("input", {
|
|
97
|
+
id: `field-${namePath}`,
|
|
98
|
+
value: picker.panel.name,
|
|
99
|
+
disabled: picker.panel.pending,
|
|
100
|
+
onKeyDown: stopEnter,
|
|
101
|
+
onChange: (event)=>picker.setName(event.currentTarget.value)
|
|
102
|
+
})
|
|
103
|
+
]
|
|
104
|
+
}),
|
|
105
|
+
/*#__PURE__*/ _jsxs("details", {
|
|
106
|
+
open: picker.panel.advanced,
|
|
107
|
+
onToggle: (event)=>picker.setAdvanced(event.currentTarget.open),
|
|
108
|
+
children: [
|
|
109
|
+
/*#__PURE__*/ _jsx("summary", {
|
|
110
|
+
children: C.advanced
|
|
111
|
+
}),
|
|
112
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
113
|
+
className: "field-type text",
|
|
114
|
+
children: [
|
|
115
|
+
/*#__PURE__*/ _jsx(FieldLabel, {
|
|
116
|
+
path: slugPath,
|
|
117
|
+
label: C.slug
|
|
118
|
+
}),
|
|
119
|
+
/*#__PURE__*/ _jsx("input", {
|
|
120
|
+
id: `field-${slugPath}`,
|
|
121
|
+
value: picker.panel.slug,
|
|
122
|
+
disabled: picker.panel.pending,
|
|
123
|
+
onKeyDown: stopEnter,
|
|
124
|
+
"aria-invalid": Boolean(picker.panel.errors.slug) || undefined,
|
|
125
|
+
"aria-describedby": picker.panel.errors.slug ? `${id}-slug-error` : undefined,
|
|
126
|
+
onChange: (event)=>picker.setSlug(event.currentTarget.value)
|
|
127
|
+
}),
|
|
128
|
+
picker.panel.errors.slug ? /*#__PURE__*/ _jsx("p", {
|
|
129
|
+
id: `${id}-slug-error`,
|
|
130
|
+
role: "alert",
|
|
131
|
+
children: picker.panel.errors.slug
|
|
132
|
+
}) : null
|
|
133
|
+
]
|
|
134
|
+
})
|
|
135
|
+
]
|
|
136
|
+
}),
|
|
137
|
+
picker.panel.errors.general ? /*#__PURE__*/ _jsx("p", {
|
|
138
|
+
role: "alert",
|
|
139
|
+
children: picker.panel.errors.general
|
|
140
|
+
}) : null,
|
|
141
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
142
|
+
className: "xp-goal-field__actions",
|
|
143
|
+
children: [
|
|
144
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
145
|
+
type: "button",
|
|
146
|
+
margin: false,
|
|
147
|
+
buttonStyle: "primary",
|
|
148
|
+
disabled: picker.panel.pending || !picker.panel.name.trim(),
|
|
149
|
+
onClick: ()=>void picker.create(),
|
|
150
|
+
children: C.createGoal
|
|
151
|
+
}),
|
|
152
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
153
|
+
type: "button",
|
|
154
|
+
margin: false,
|
|
155
|
+
buttonStyle: "secondary",
|
|
156
|
+
disabled: picker.panel.pending,
|
|
157
|
+
onClick: picker.cancel,
|
|
158
|
+
children: C.cancel
|
|
159
|
+
})
|
|
160
|
+
]
|
|
161
|
+
})
|
|
162
|
+
]
|
|
163
|
+
}) : null
|
|
164
|
+
]
|
|
165
|
+
}) : null
|
|
166
|
+
]
|
|
167
|
+
});
|
|
168
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.xp-goal-field {
|
|
2
|
+
display: grid;
|
|
3
|
+
gap: var(--xp-field-gap);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.xp-goal-field__row {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
align-items: end;
|
|
10
|
+
gap: var(--xp-space-2);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.xp-goal-field__select {
|
|
14
|
+
flex: 1 1 auto;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.xp-goal-field__panel {
|
|
19
|
+
display: grid;
|
|
20
|
+
gap: var(--xp-field-gap);
|
|
21
|
+
border: 1px solid var(--xp-border);
|
|
22
|
+
border-radius: var(--xp-radius-control);
|
|
23
|
+
padding: var(--xp-space-4);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.xp-goal-field > p,
|
|
27
|
+
.xp-goal-field__panel > h3,
|
|
28
|
+
.xp-goal-field__panel > p {
|
|
29
|
+
margin: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.xp-goal-field__panel details[open] > summary {
|
|
33
|
+
margin-bottom: var(--xp-field-gap);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.xp-goal-field__actions {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-wrap: wrap;
|
|
39
|
+
gap: var(--xp-space-2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Payload halves the gap between fields below 768px; match it inside the field. */
|
|
43
|
+
@media (max-width: 768px) {
|
|
44
|
+
.xp-goal-field,
|
|
45
|
+
.xp-goal-field__panel {
|
|
46
|
+
gap: calc(var(--xp-field-gap) / 2);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -5,3 +5,5 @@ export declare function metricSourceOptions(sources: readonly SerializedMetricSo
|
|
|
5
5
|
badge: "count" | "yes/no" | "amount";
|
|
6
6
|
}[];
|
|
7
7
|
export declare function metricSourceHint(source?: SerializedMetricSource): string | null;
|
|
8
|
+
/** Click goal definitions share the serialized click-source option. */
|
|
9
|
+
export declare function sourceFor(sourceId: string | undefined, metricSources: readonly SerializedMetricSource[]): SerializedMetricSource | undefined;
|
|
@@ -9,3 +9,7 @@ export function metricSourceOptions(sources) {
|
|
|
9
9
|
export function metricSourceHint(source) {
|
|
10
10
|
return source?.kind === 'revenue' ? METRICS_COPY.revenueHint : null;
|
|
11
11
|
}
|
|
12
|
+
/** Click goal definitions share the serialized click-source option. */ export function sourceFor(sourceId, metricSources) {
|
|
13
|
+
const id = sourceId?.startsWith('click:') ? 'click:' : sourceId;
|
|
14
|
+
return metricSources.find((source)=>source.id === id);
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { ExperimentsPageCollection } from '../types/contracts.js';
|
|
3
|
+
export declare function serializePageCollections(collections: ExperimentsPageCollection[], req: PayloadRequest): {
|
|
4
|
+
copyUrl: string;
|
|
5
|
+
copyLabel: string;
|
|
6
|
+
copyHint?: string | undefined;
|
|
7
|
+
slug: string;
|
|
8
|
+
label: string;
|
|
9
|
+
contentChecks: boolean;
|
|
10
|
+
}[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { formatAdminURL } from 'payload/shared';
|
|
2
|
+
import { collectionLabel } from '../collections/labels.js';
|
|
3
|
+
export function serializePageCollections(collections, req) {
|
|
4
|
+
return collections.map((entry)=>{
|
|
5
|
+
const label = collectionLabel(req.payload.config.collections.find((collection)=>collection.slug === entry.slug)?.labels.plural, entry.slug, req);
|
|
6
|
+
return {
|
|
7
|
+
slug: entry.slug,
|
|
8
|
+
label,
|
|
9
|
+
contentChecks: typeof entry.contentOf === 'function',
|
|
10
|
+
...entry.copyHint ? {
|
|
11
|
+
copyHint: entry.copyHint
|
|
12
|
+
} : {},
|
|
13
|
+
copyUrl: entry.copyUrl ?? formatAdminURL({
|
|
14
|
+
adminRoute: req.payload.config.routes.admin,
|
|
15
|
+
path: `/collections/${entry.slug}`
|
|
16
|
+
}),
|
|
17
|
+
copyLabel: entry.copyLabel ?? `Open ${label}`
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
package/dist/admin/paths.d.ts
CHANGED
|
@@ -8,3 +8,12 @@ export declare const XP_TESTS_NAV_LINK = "@delmaredigital/payload-experiments/ad
|
|
|
8
8
|
export declare const XP_TESTS_NAV_CLIENT = "@delmaredigital/payload-experiments/admin/client#TestsNavLink";
|
|
9
9
|
export declare const XP_TESTS_SURFACE = "@delmaredigital/payload-experiments/admin/client#TestsSurface";
|
|
10
10
|
export declare const XP_TESTS_DASHBOARD_CARDS = "@delmaredigital/payload-experiments/admin/client#TestsDashboardCards";
|
|
11
|
+
export declare const XP_PLACEMENT_STATUS = "@delmaredigital/payload-experiments/admin/client#PlacementStatus";
|
|
12
|
+
export declare const XP_TEST_BLOCK = "@delmaredigital/payload-experiments/admin/client#TestBlockField";
|
|
13
|
+
export declare const XP_BLOCK_STATUS = "@delmaredigital/payload-experiments/admin/client#BlockStatusField";
|
|
14
|
+
export declare const XP_BLOCK_ROW_LABEL = "@delmaredigital/payload-experiments/admin/client#BlockSlotRowLabel";
|
|
15
|
+
export declare const XP_COPY_ORIGINAL = "@delmaredigital/payload-experiments/admin/client#CopyOriginalField";
|
|
16
|
+
export declare const XP_GOAL_FIELD = "@delmaredigital/payload-experiments/admin/client#XpGoalField";
|
|
17
|
+
export declare const XP_TESTS_COLLECTION_LIST = "@delmaredigital/payload-experiments/admin/rsc#TestsCollectionListView";
|
|
18
|
+
export declare const XP_GOALS_COLLECTION_LIST = "@delmaredigital/payload-experiments/admin/rsc#GoalsCollectionListView";
|
|
19
|
+
export declare const XP_GOAL_EDIT_REDIRECT = "@delmaredigital/payload-experiments/admin/rsc#GoalEditRedirect";
|
package/dist/admin/paths.js
CHANGED
|
@@ -8,3 +8,12 @@ export const XP_TESTS_NAV_LINK = '@delmaredigital/payload-experiments/admin/rsc#
|
|
|
8
8
|
export const XP_TESTS_NAV_CLIENT = '@delmaredigital/payload-experiments/admin/client#TestsNavLink';
|
|
9
9
|
export const XP_TESTS_SURFACE = '@delmaredigital/payload-experiments/admin/client#TestsSurface';
|
|
10
10
|
export const XP_TESTS_DASHBOARD_CARDS = '@delmaredigital/payload-experiments/admin/client#TestsDashboardCards';
|
|
11
|
+
export const XP_PLACEMENT_STATUS = '@delmaredigital/payload-experiments/admin/client#PlacementStatus';
|
|
12
|
+
export const XP_TEST_BLOCK = '@delmaredigital/payload-experiments/admin/client#TestBlockField';
|
|
13
|
+
export const XP_BLOCK_STATUS = '@delmaredigital/payload-experiments/admin/client#BlockStatusField';
|
|
14
|
+
export const XP_BLOCK_ROW_LABEL = '@delmaredigital/payload-experiments/admin/client#BlockSlotRowLabel';
|
|
15
|
+
export const XP_COPY_ORIGINAL = '@delmaredigital/payload-experiments/admin/client#CopyOriginalField';
|
|
16
|
+
export const XP_GOAL_FIELD = '@delmaredigital/payload-experiments/admin/client#XpGoalField';
|
|
17
|
+
export const XP_TESTS_COLLECTION_LIST = '@delmaredigital/payload-experiments/admin/rsc#TestsCollectionListView';
|
|
18
|
+
export const XP_GOALS_COLLECTION_LIST = '@delmaredigital/payload-experiments/admin/rsc#GoalsCollectionListView';
|
|
19
|
+
export const XP_GOAL_EDIT_REDIRECT = '@delmaredigital/payload-experiments/admin/rsc#GoalEditRedirect';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function pageStatusCopy(test) {
|
|
2
|
+
if (test.pagesReleased) return null;
|
|
3
|
+
const winner = `${test.winnerLabel ?? 'the winner'}${test.winnerTitle ? ` (${test.winnerTitle})` : ''}`;
|
|
4
|
+
switch(test.status){
|
|
5
|
+
case 'draft':
|
|
6
|
+
return `This page is ${test.armLabel} in the draft test ${test.name}.`;
|
|
7
|
+
case 'stopped':
|
|
8
|
+
{
|
|
9
|
+
const visitors = test.role === 'original' ? 'Visitors see this page.' : !test.originalPath ? 'Visitors see the Original.' : `Visitors to ${test.originalPath === '/' ? 'the homepage (/)' : test.originalPath} see the Original.`;
|
|
10
|
+
return `This page is ${test.armLabel} in the stopped test ${test.name}. ${visitors}`;
|
|
11
|
+
}
|
|
12
|
+
case 'running':
|
|
13
|
+
return `This page is the ${test.armLabel} of a running test: ${test.name}.`;
|
|
14
|
+
case 'decided':
|
|
15
|
+
return `This page is ${test.armLabel} of a decided test; ${test.rollout ? `most visitors see ${winner}; a holdout still sees the Original` : `visitors see ${winner}`}.`;
|
|
16
|
+
case 'archived':
|
|
17
|
+
return test.winner ? `This page is also shown at the other versions' addresses because test ${test.name} made it the winner. To free it, open the test and choose "Release pages".` : test.role === 'original' ? `This page's address now shows the winner of test ${test.name}. To free it, open the test and choose "Release pages".` : `This page's address now shows ${winner}. To show this page again, open the test and choose "Release pages".`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DocId } from '../../types/contracts.js';
|
|
2
|
+
import type { PreflightResult } from '../../types/endpoints.js';
|
|
3
|
+
export { placementDetailKey, placementDetail, fallbackPlacementLabel } from './placementLabels.model.js';
|
|
4
|
+
export type { PlacementLabel } from './placementLabels.model.js';
|
|
5
|
+
import type { PlacementLabel } from './placementLabels.model.js';
|
|
6
|
+
export declare function resolvedPlacementLabel(value: unknown, match: {
|
|
7
|
+
id?: DocId | null;
|
|
8
|
+
title?: string;
|
|
9
|
+
editorUrl?: string;
|
|
10
|
+
} | undefined, adminRoute: string): PlacementLabel;
|
|
11
|
+
export declare function preflightPlacementLabel(value: unknown, preflight: PreflightResult, adminRoute: string): PlacementLabel;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { formatAdminURL } from 'payload/shared';
|
|
2
|
+
import { placementDetail } from './placementLabels.model.js';
|
|
3
|
+
export { placementDetailKey, placementDetail, fallbackPlacementLabel } from './placementLabels.model.js';
|
|
4
|
+
export function resolvedPlacementLabel(value, match, adminRoute) {
|
|
5
|
+
const detail = placementDetail(value);
|
|
6
|
+
const id = match?.id ?? detail.id;
|
|
7
|
+
return {
|
|
8
|
+
title: match?.title?.trim() || detail.path || 'This page',
|
|
9
|
+
...id != null && detail.collection ? {
|
|
10
|
+
href: match?.editorUrl || formatAdminURL({
|
|
11
|
+
adminRoute,
|
|
12
|
+
path: `/collections/${encodeURIComponent(detail.collection)}/${encodeURIComponent(String(id))}`
|
|
13
|
+
})
|
|
14
|
+
} : {}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function preflightPlacementLabel(value, preflight, adminRoute) {
|
|
18
|
+
const detail = placementDetail(value);
|
|
19
|
+
const match = detail.collection ? detail.id != null ? preflight.placements.find((item)=>item.collection === detail.collection && String(item.id) === String(detail.id)) : typeof detail.path === 'string' ? preflight.pagePlacements?.find((item)=>item.collection === detail.collection && item.path === detail.path) : undefined : undefined;
|
|
20
|
+
return resolvedPlacementLabel(value, match, adminRoute);
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DocId } from '../../types/contracts.js';
|
|
2
|
+
export type PlacementDetail = {
|
|
3
|
+
collection?: string;
|
|
4
|
+
id?: DocId;
|
|
5
|
+
path?: string;
|
|
6
|
+
};
|
|
7
|
+
export type PlacementLabel = {
|
|
8
|
+
title: string;
|
|
9
|
+
href?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const placementDetail: (detail: unknown) => PlacementDetail;
|
|
12
|
+
export declare function placementDetailKey(value: unknown): string;
|
|
13
|
+
export declare const fallbackPlacementLabel: (value: unknown) => PlacementLabel;
|
|
@@ -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({
|