@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
|
@@ -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 { surfaceUrls } from '../surfaceUrls.js';
|
|
3
4
|
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
-
import { Link } from '@payloadcms/ui';
|
|
5
5
|
import { formatAdminURL } from 'payload/shared';
|
|
6
6
|
import { fetchAllDocs } from '../../lib/fetchAllDocs.js';
|
|
7
7
|
import { createApi } from '../api.js';
|
|
@@ -10,6 +10,7 @@ import { Banner, EmptyState, StatusChip } from '../../ui/Feedback.js';
|
|
|
10
10
|
import { Button } from '../../ui/Button.js';
|
|
11
11
|
import { SearchInput } from '../../ui/Inputs.js';
|
|
12
12
|
import { SurfaceShell } from './SurfaceShell.js';
|
|
13
|
+
import { sanitizeReadinessDetail } from '../../sanitizeReadinessDetail.js';
|
|
13
14
|
import { groupTests, homeCard } from './home.model.js';
|
|
14
15
|
const HOME_FIELDS = [
|
|
15
16
|
'name',
|
|
@@ -52,11 +53,40 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
52
53
|
const docs = response.docs.sort((a, b)=>(Date.parse(b.updatedAt ?? '') || 0) - (Date.parse(a.updatedAt ?? '') || 0));
|
|
53
54
|
if (!active) return;
|
|
54
55
|
const summary = await api.summary(docs.map((doc)=>doc.id));
|
|
56
|
+
const summaries = summary.ok ? summary.data ?? {} : {};
|
|
57
|
+
const ids = [
|
|
58
|
+
...new Set(docs.flatMap((doc)=>{
|
|
59
|
+
const item = summaries[doc.id];
|
|
60
|
+
if (!canManage || ![
|
|
61
|
+
'draft',
|
|
62
|
+
'stopped'
|
|
63
|
+
].includes(doc.status) || item?.ready === true || item?.next?.code !== 'goal-in-some-versions') return [];
|
|
64
|
+
const detail = sanitizeReadinessDetail(item.next.code, item.next.detail);
|
|
65
|
+
return detail?.map((row)=>String(row.metricId)) ?? [];
|
|
66
|
+
}))
|
|
67
|
+
];
|
|
68
|
+
const goalNames = {};
|
|
69
|
+
if (ids.length) {
|
|
70
|
+
try {
|
|
71
|
+
const query = new URLSearchParams({
|
|
72
|
+
'where[id][in]': ids.join(','),
|
|
73
|
+
depth: '0',
|
|
74
|
+
limit: String(ids.length)
|
|
75
|
+
});
|
|
76
|
+
const response = await fetch(`${apiRoute.replace(/\/$/, '')}/xp-metrics?${query}`, {
|
|
77
|
+
credentials: 'include',
|
|
78
|
+
signal: controller.signal
|
|
79
|
+
});
|
|
80
|
+
const body = response.ok ? await response.json() : undefined;
|
|
81
|
+
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;
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
55
84
|
if (active) setState({
|
|
56
85
|
loading: false,
|
|
57
86
|
error: false,
|
|
58
87
|
tests: docs,
|
|
59
|
-
|
|
88
|
+
goalNames,
|
|
89
|
+
summaries,
|
|
60
90
|
summaryError: !summary.ok
|
|
61
91
|
});
|
|
62
92
|
} catch {
|
|
@@ -74,7 +104,8 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
74
104
|
}, [
|
|
75
105
|
api,
|
|
76
106
|
apiRoute,
|
|
77
|
-
attempt
|
|
107
|
+
attempt,
|
|
108
|
+
canManage
|
|
78
109
|
]);
|
|
79
110
|
const retry = /*#__PURE__*/ _jsx(Button, {
|
|
80
111
|
size: "sm",
|
|
@@ -124,10 +155,7 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
124
155
|
]
|
|
125
156
|
}),
|
|
126
157
|
canManage ? /*#__PURE__*/ _jsx(Button, {
|
|
127
|
-
href:
|
|
128
|
-
adminRoute,
|
|
129
|
-
path: '/experiments/goals'
|
|
130
|
-
}),
|
|
158
|
+
href: surfaceUrls.goals(adminRoute),
|
|
131
159
|
children: SURFACE_COPY.goals
|
|
132
160
|
}) : null,
|
|
133
161
|
newTest
|
|
@@ -153,6 +181,7 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
153
181
|
}) : /*#__PURE__*/ _jsx(TestGroups, {
|
|
154
182
|
tests: state.tests,
|
|
155
183
|
summaries: state.summaries,
|
|
184
|
+
goalNames: state.goalNames,
|
|
156
185
|
adminRoute: adminRoute,
|
|
157
186
|
flagPoints: flagPoints,
|
|
158
187
|
search: search,
|
|
@@ -162,7 +191,7 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
162
191
|
})
|
|
163
192
|
});
|
|
164
193
|
}
|
|
165
|
-
function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear }) {
|
|
194
|
+
function TestGroups({ tests, summaries, goalNames, adminRoute, flagPoints, search, clear }) {
|
|
166
195
|
const groups = groupTests(tests, summaries, search);
|
|
167
196
|
if (!groups.some((group)=>group.tests.length)) return /*#__PURE__*/ _jsxs("div", {
|
|
168
197
|
className: "xp-tests-no-matches",
|
|
@@ -184,7 +213,7 @@ function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear })
|
|
|
184
213
|
const rows = /*#__PURE__*/ _jsx("div", {
|
|
185
214
|
className: "xp-tests-list",
|
|
186
215
|
children: group.tests.map((test)=>{
|
|
187
|
-
const card = homeCard(test, summaries[test.id], adminRoute, flagPoints);
|
|
216
|
+
const card = homeCard(test, summaries[test.id], adminRoute, flagPoints, goalNames);
|
|
188
217
|
return /*#__PURE__*/ _jsxs("article", {
|
|
189
218
|
className: "xp-tests-row",
|
|
190
219
|
children: [
|
|
@@ -194,7 +223,7 @@ function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear })
|
|
|
194
223
|
/*#__PURE__*/ _jsxs("div", {
|
|
195
224
|
className: "xp-tests-row-name",
|
|
196
225
|
children: [
|
|
197
|
-
/*#__PURE__*/ _jsx(
|
|
226
|
+
/*#__PURE__*/ _jsx("a", {
|
|
198
227
|
href: card.href,
|
|
199
228
|
children: test.name
|
|
200
229
|
}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { surfaceUrls } from '../surfaceUrls.js';
|
|
3
4
|
import { useState } from 'react';
|
|
4
|
-
import { formatAdminURL } from 'payload/shared';
|
|
5
5
|
import { SURFACE_COPY } from '../copy/surface.js';
|
|
6
6
|
import { NewTest } from './newTest/NewTest.js';
|
|
7
7
|
import { TestPage } from './test/TestPage.js';
|
|
@@ -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
|
{
|
|
@@ -36,10 +40,7 @@ function SurfacePage(props) {
|
|
|
36
40
|
}
|
|
37
41
|
];
|
|
38
42
|
if (props.route !== 'home') {
|
|
39
|
-
crumbs[0].href =
|
|
40
|
-
adminRoute: props.adminRoute,
|
|
41
|
-
path: '/experiments'
|
|
42
|
-
});
|
|
43
|
+
crumbs[0].href = props.canManage ? surfaceUrls.home(props.adminRoute) : undefined;
|
|
43
44
|
crumbs.push({
|
|
44
45
|
label: props.route === 'new' ? SURFACE_COPY.newTest : props.route === 'results' ? SURFACE_COPY.resultsTitle : SURFACE_COPY.test
|
|
45
46
|
});
|
|
@@ -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,14 @@
|
|
|
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
|
+
import { surfaceUrls } from '../../surfaceUrls.js';
|
|
3
4
|
import { useEffect, useRef, useState } from 'react';
|
|
4
5
|
import { useRouter } from 'next/navigation.js';
|
|
5
6
|
import { formatAdminURL } from 'payload/shared';
|
|
6
7
|
import { GOALS_COPY as C, goalDeleteLocked, goalInUse } from '../../copy/goals.js';
|
|
7
|
-
import { metricSourceHint, metricSourceOptions } from '../../metricSource.model.js';
|
|
8
|
+
import { metricSourceHint, metricSourceOptions, sourceFor } from '../../metricSource.model.js';
|
|
8
9
|
import { validateCappingType, validateCappingValue } from '../../../collections/metricValidation.js';
|
|
10
|
+
import { CLICK_KEY } from '../../../goals/key.js';
|
|
11
|
+
import { clickGoalSlug } from '../../../goals/picker/clickGoalPicker.model.js';
|
|
9
12
|
import { goalSlug, goalUsage } from './goals.model.js';
|
|
10
13
|
import { GoalResponseError, goalPages, goalResponse } from './data.js';
|
|
11
14
|
import { SURFACE_COPY } from '../../copy/surface.js';
|
|
@@ -55,17 +58,13 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
55
58
|
const advanced = useRef(null);
|
|
56
59
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
57
60
|
useEffect(()=>{
|
|
58
|
-
if (advanced.current && (fieldErrors.capType || fieldErrors.capValue || fieldErrors.warningPercent)) advanced.current.open = true;
|
|
61
|
+
if (advanced.current && (fieldErrors.slug || fieldErrors.capType || fieldErrors.capValue || fieldErrors.warningPercent)) advanced.current.open = true;
|
|
59
62
|
}, [
|
|
60
63
|
fieldErrors
|
|
61
64
|
]);
|
|
62
|
-
const
|
|
65
|
+
const listUrl = surfaceUrls.goals(surface.adminRoute, returnToTest ? {
|
|
63
66
|
return: returnToTest
|
|
64
|
-
}
|
|
65
|
-
const listUrl = formatAdminURL({
|
|
66
|
-
adminRoute: surface.adminRoute,
|
|
67
|
-
path: `/experiments/goals${returnQuery}`
|
|
68
|
-
});
|
|
67
|
+
} : undefined);
|
|
69
68
|
useEffect(()=>{
|
|
70
69
|
const controller = new AbortController();
|
|
71
70
|
setReady(false);
|
|
@@ -84,7 +83,7 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
84
83
|
]);
|
|
85
84
|
if (loaded.id == null) throw new Error(C.missing);
|
|
86
85
|
if (controller.signal.aborted) return;
|
|
87
|
-
const defaultCap =
|
|
86
|
+
const defaultCap = sourceFor(loaded.source, surface.metricSources)?.kind === 'revenue' && loaded.capping?.type == null && loaded.capping?.value == null;
|
|
88
87
|
setGoal(loaded);
|
|
89
88
|
setUsedBy(goalUsage(tests).get(loaded.id) ?? 0);
|
|
90
89
|
setDraft({
|
|
@@ -107,7 +106,8 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
107
106
|
id,
|
|
108
107
|
attempt
|
|
109
108
|
]);
|
|
110
|
-
const source =
|
|
109
|
+
const source = sourceFor(draft.source, surface.metricSources);
|
|
110
|
+
const click = draft.source.startsWith('click:');
|
|
111
111
|
const hint = metricSourceHint(source);
|
|
112
112
|
const options = metricSourceOptions(surface.metricSources);
|
|
113
113
|
const set = (key, value)=>setDraft((current)=>({
|
|
@@ -116,11 +116,14 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
116
116
|
}));
|
|
117
117
|
async function submit() {
|
|
118
118
|
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);
|
|
119
|
+
const capValue = click || draft.capValue.trim() === '' ? null : Number(draft.capValue);
|
|
120
|
+
const typeError = validateCappingType(click ? null : draft.capType || null, capValue, source?.kind);
|
|
121
|
+
const valueError = validateCappingValue(capValue, click ? null : draft.capType || null);
|
|
122
122
|
const warningPercent = draft.warningPercent.trim() === '' ? 5 : Number(draft.warningPercent);
|
|
123
123
|
const errors = {
|
|
124
|
+
...click && !CLICK_KEY.test(draft.slug) ? {
|
|
125
|
+
slug: C.slugRule
|
|
126
|
+
} : {},
|
|
124
127
|
...typeError !== true ? {
|
|
125
128
|
capType: typeError
|
|
126
129
|
} : {},
|
|
@@ -141,12 +144,12 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
141
144
|
const body = {
|
|
142
145
|
name: draft.name,
|
|
143
146
|
slug: usedBy && goal ? goal.slug : draft.slug,
|
|
144
|
-
source: usedBy && goal ? goal.source : draft.source,
|
|
145
|
-
direction: draft.direction,
|
|
147
|
+
source: usedBy && goal ? goal.source : click ? `click:${draft.slug}` : draft.source,
|
|
148
|
+
direction: click ? 'increase' : draft.direction,
|
|
146
149
|
attributionWindowHours: draft.attributionWindowHours,
|
|
147
150
|
minSampleSize: draft.minSampleSize,
|
|
148
151
|
capping: {
|
|
149
|
-
type: draft.capType || null,
|
|
152
|
+
type: click ? null : draft.capType || null,
|
|
150
153
|
value: capValue
|
|
151
154
|
},
|
|
152
155
|
guardrailThreshold: warningPercent / 100
|
|
@@ -170,11 +173,11 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
170
173
|
})}`
|
|
171
174
|
}) : listUrl);
|
|
172
175
|
} catch (cause) {
|
|
173
|
-
setError(cause instanceof
|
|
176
|
+
setError(cause instanceof GoalResponseError ? cause.message : C.saveError);
|
|
174
177
|
if (cause instanceof GoalResponseError) setFieldErrors(Object.fromEntries(Object.entries(cause.fields).flatMap(([path, message])=>fieldNames[path] ? [
|
|
175
178
|
[
|
|
176
179
|
fieldNames[path],
|
|
177
|
-
message
|
|
180
|
+
path === 'slug' ? C.duplicateSlug : message
|
|
178
181
|
]
|
|
179
182
|
] : [])));
|
|
180
183
|
inFlight.current = false;
|
|
@@ -209,10 +212,7 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
209
212
|
crumbs: [
|
|
210
213
|
{
|
|
211
214
|
label: SURFACE_COPY.title,
|
|
212
|
-
href:
|
|
213
|
-
adminRoute: surface.adminRoute,
|
|
214
|
-
path: '/experiments'
|
|
215
|
-
})
|
|
215
|
+
href: surfaceUrls.home(surface.adminRoute)
|
|
216
216
|
},
|
|
217
217
|
{
|
|
218
218
|
label: C.title,
|
|
@@ -272,41 +272,54 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
272
272
|
...current,
|
|
273
273
|
name,
|
|
274
274
|
...!slugEdited && !usedBy ? {
|
|
275
|
-
slug: goalSlug(name)
|
|
275
|
+
slug: click ? clickGoalSlug(name) : goalSlug(name)
|
|
276
276
|
} : {}
|
|
277
277
|
}));
|
|
278
|
+
if (click && !slugEdited && !usedBy) setFieldErrors((current)=>({
|
|
279
|
+
...current,
|
|
280
|
+
slug: clickGoalSlug(name) ? undefined : C.slugRule
|
|
281
|
+
}));
|
|
278
282
|
}
|
|
279
283
|
}),
|
|
280
|
-
/*#__PURE__*/ _jsx(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
284
|
+
!click ? /*#__PURE__*/ _jsx(_Fragment, {
|
|
285
|
+
children: /*#__PURE__*/ _jsx(TextInput, {
|
|
286
|
+
id: "xp-goal-slug",
|
|
287
|
+
label: C.slug,
|
|
288
|
+
required: true,
|
|
289
|
+
value: draft.slug,
|
|
290
|
+
readOnly: usedBy > 0,
|
|
291
|
+
help: usedBy ? C.slugLocked : C.slugHelp,
|
|
292
|
+
error: fieldErrors.slug,
|
|
293
|
+
onChange: (event)=>{
|
|
294
|
+
setSlugEdited(true);
|
|
295
|
+
set('slug', event.currentTarget.value);
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
}) : null,
|
|
293
299
|
/*#__PURE__*/ _jsxs(Select, {
|
|
294
300
|
id: "xp-goal-source",
|
|
295
301
|
label: C.source,
|
|
296
302
|
required: true,
|
|
297
|
-
value: draft.source,
|
|
303
|
+
value: click ? 'click:' : draft.source,
|
|
298
304
|
disabled: usedBy > 0,
|
|
299
305
|
help: usedBy ? C.sourceLocked : hint || undefined,
|
|
300
306
|
error: fieldErrors.source,
|
|
301
307
|
onChange: (event)=>{
|
|
302
308
|
const sourceId = event.currentTarget.value;
|
|
303
|
-
const revenue = surface.metricSources
|
|
309
|
+
const revenue = sourceFor(sourceId, surface.metricSources)?.kind === 'revenue';
|
|
304
310
|
setDraft((current)=>({
|
|
305
311
|
...current,
|
|
306
312
|
source: sourceId,
|
|
313
|
+
...!slugEdited && !usedBy ? {
|
|
314
|
+
slug: sourceId === 'click:' ? clickGoalSlug(current.name) : goalSlug(current.name)
|
|
315
|
+
} : {},
|
|
307
316
|
capType: revenue ? 'percentile' : '',
|
|
308
317
|
capValue: revenue ? '0.99' : ''
|
|
309
318
|
}));
|
|
319
|
+
if (sourceId === 'click:' && !slugEdited && !usedBy) setFieldErrors((current)=>({
|
|
320
|
+
...current,
|
|
321
|
+
slug: clickGoalSlug(draft.name) ? undefined : C.slugRule
|
|
322
|
+
}));
|
|
310
323
|
},
|
|
311
324
|
children: [
|
|
312
325
|
/*#__PURE__*/ _jsx("option", {
|
|
@@ -315,15 +328,15 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
315
328
|
}),
|
|
316
329
|
options.map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
317
330
|
value: option.value,
|
|
318
|
-
children: option.label
|
|
331
|
+
children: option.value === 'click:' ? sourceFor(option.value, surface.metricSources)?.label : option.label
|
|
319
332
|
}, option.value)),
|
|
320
|
-
draft.source && !options.some((option)=>option.value === draft.source) ? /*#__PURE__*/ _jsx("option", {
|
|
333
|
+
!click && draft.source && !options.some((option)=>option.value === draft.source) ? /*#__PURE__*/ _jsx("option", {
|
|
321
334
|
value: draft.source,
|
|
322
335
|
children: draft.source
|
|
323
336
|
}) : null
|
|
324
337
|
]
|
|
325
338
|
}),
|
|
326
|
-
/*#__PURE__*/ _jsxs(Select, {
|
|
339
|
+
!click ? /*#__PURE__*/ _jsxs(Select, {
|
|
327
340
|
id: "xp-goal-direction",
|
|
328
341
|
label: C.direction,
|
|
329
342
|
value: draft.direction,
|
|
@@ -339,7 +352,7 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
339
352
|
children: C.decrease
|
|
340
353
|
})
|
|
341
354
|
]
|
|
342
|
-
}),
|
|
355
|
+
}) : null,
|
|
343
356
|
/*#__PURE__*/ _jsx(NumberInput, {
|
|
344
357
|
id: "xp-goal-window",
|
|
345
358
|
label: C.window,
|
|
@@ -370,7 +383,21 @@ export function GoalForm({ surface, id, returnToTest }) {
|
|
|
370
383
|
/*#__PURE__*/ _jsxs("div", {
|
|
371
384
|
className: "xp-goal-advanced-fields",
|
|
372
385
|
children: [
|
|
373
|
-
/*#__PURE__*/
|
|
386
|
+
click ? /*#__PURE__*/ _jsx(_Fragment, {
|
|
387
|
+
children: /*#__PURE__*/ _jsx(TextInput, {
|
|
388
|
+
id: "xp-goal-slug",
|
|
389
|
+
label: C.slug,
|
|
390
|
+
required: true,
|
|
391
|
+
value: draft.slug,
|
|
392
|
+
readOnly: usedBy > 0,
|
|
393
|
+
help: usedBy ? C.slugLocked : C.slugHelp,
|
|
394
|
+
error: fieldErrors.slug,
|
|
395
|
+
onChange: (event)=>{
|
|
396
|
+
setSlugEdited(true);
|
|
397
|
+
set('slug', event.currentTarget.value);
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
}) : /*#__PURE__*/ _jsxs("fieldset", {
|
|
374
401
|
children: [
|
|
375
402
|
/*#__PURE__*/ _jsx("legend", {
|
|
376
403
|
children: C.capping
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { surfaceUrls } from '../../surfaceUrls.js';
|
|
4
|
+
import { sourceFor } from '../../metricSource.model.js';
|
|
3
5
|
import { useEffect, useState } from 'react';
|
|
4
|
-
import { Link } from '@payloadcms/ui';
|
|
5
6
|
import { formatAdminURL } from 'payload/shared';
|
|
6
7
|
import { Banner, EmptyState } from '../../../ui/Feedback.js';
|
|
7
8
|
import { Button } from '../../../ui/Button.js';
|
|
@@ -60,7 +61,7 @@ export function GoalsList({ surface, returnToTest }) {
|
|
|
60
61
|
crumbs: [
|
|
61
62
|
{
|
|
62
63
|
label: SURFACE_COPY.title,
|
|
63
|
-
href:
|
|
64
|
+
href: surfaceUrls.home(surface.adminRoute)
|
|
64
65
|
},
|
|
65
66
|
{
|
|
66
67
|
label: C.title
|
|
@@ -111,7 +112,7 @@ export function GoalsList({ surface, returnToTest }) {
|
|
|
111
112
|
className: "xp-tests-list",
|
|
112
113
|
children: goals.map((goal)=>{
|
|
113
114
|
const count = usage.get(goal.id) ?? 0;
|
|
114
|
-
const kind =
|
|
115
|
+
const kind = sourceFor(goal.source, surface.metricSources)?.kind;
|
|
115
116
|
const label = kind && Object.hasOwn(METRIC_KIND_LABELS, kind) ? METRIC_KIND_LABELS[kind] : undefined;
|
|
116
117
|
const href = url(`/experiments/goals/${encodeURIComponent(String(goal.id))}${returnQuery}`);
|
|
117
118
|
return /*#__PURE__*/ _jsxs("article", {
|
|
@@ -123,7 +124,7 @@ export function GoalsList({ surface, returnToTest }) {
|
|
|
123
124
|
/*#__PURE__*/ _jsxs("div", {
|
|
124
125
|
className: "xp-tests-row-name",
|
|
125
126
|
children: [
|
|
126
|
-
/*#__PURE__*/ _jsx(
|
|
127
|
+
/*#__PURE__*/ _jsx("a", {
|
|
127
128
|
href: href,
|
|
128
129
|
children: goal.name
|
|
129
130
|
}),
|
|
@@ -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';
|
|
@@ -23,11 +23,11 @@ type Summary = SummaryResponse[DocId] | undefined;
|
|
|
23
23
|
export type Health = 'red' | 'amber' | 'green';
|
|
24
24
|
export declare function healthDot(test: HomeTest, summary: Summary): Health | undefined;
|
|
25
25
|
export declare function groupTests(tests: readonly HomeTest[], summaries: SummaryResponse, search?: string): {
|
|
26
|
-
label: "Needs attention" | "Running" | "Decided" | "
|
|
26
|
+
label: "Needs attention" | "Running" | "Decided" | "Ready to start" | "Draft" | "Archived";
|
|
27
27
|
tests: HomeTest[];
|
|
28
28
|
}[];
|
|
29
|
-
export declare function homeCard(test: HomeTest, summary: Summary, adminRoute: string, flagPoints?: ReadinessContext['flagPoints']): {
|
|
30
|
-
kind: "
|
|
29
|
+
export declare function homeCard(test: HomeTest, summary: Summary, adminRoute: string, flagPoints?: ReadinessContext['flagPoints'], goalNames?: Readonly<Record<string, string>>): {
|
|
30
|
+
kind: "Page block" | "Setting" | "Whole page";
|
|
31
31
|
pill: {
|
|
32
32
|
label: string;
|
|
33
33
|
tone: import("../status.model.js").PillTone;
|
|
@@ -3,7 +3,7 @@ import { formatDate } from '../format/date.js';
|
|
|
3
3
|
import { armLabel } from '../labels.js';
|
|
4
4
|
import { statusPill } from '../status.model.js';
|
|
5
5
|
import { decidedLine, runningLine, SURFACE_COPY } from '../copy/surface.js';
|
|
6
|
-
import { nextCopy } from '../copy/readiness.js';
|
|
6
|
+
import { asymmetricGoalsCopy, nextCopy, READINESS_COPY } from '../copy/readiness.js';
|
|
7
7
|
import { READINESS } from './readiness/readiness.model.js';
|
|
8
8
|
import { sanitizeReadinessDetail } from '../../sanitizeReadinessDetail.js';
|
|
9
9
|
export function healthDot(test, summary) {
|
|
@@ -30,7 +30,7 @@ export function groupTests(tests, summaries, search = '') {
|
|
|
30
30
|
const number = (value)=>value != null && Number.isFinite(value) ? new Intl.NumberFormat('en-US', {
|
|
31
31
|
maximumFractionDigits: 0
|
|
32
32
|
}).format(value) : '—';
|
|
33
|
-
export function homeCard(test, summary, adminRoute, flagPoints) {
|
|
33
|
+
export function homeCard(test, summary, adminRoute, flagPoints, goalNames = {}) {
|
|
34
34
|
const results = [
|
|
35
35
|
'running',
|
|
36
36
|
'stopped',
|
|
@@ -45,12 +45,16 @@ export function homeCard(test, summary, adminRoute, flagPoints) {
|
|
|
45
45
|
const next = summary?.next;
|
|
46
46
|
if ((test.status === 'draft' || test.status === 'stopped') && summary?.ready !== true && next && Object.hasOwn(READINESS, next.code)) {
|
|
47
47
|
// Keep unexpected text out of authored copy, including responses from older servers.
|
|
48
|
-
const
|
|
48
|
+
const detail = sanitizeReadinessDetail(next.code, next.detail);
|
|
49
|
+
const first = Array.isArray(detail) ? detail[0] : undefined;
|
|
50
|
+
const sentence = next.code === 'goal-in-some-versions' ? asymmetricGoalsCopy({
|
|
51
|
+
name: first?.metricId == null ? undefined : goalNames[String(first.metricId)]
|
|
52
|
+
}) : READINESS[next.code].next(detail, {
|
|
49
53
|
...test,
|
|
50
54
|
arms: test.arms ?? [],
|
|
51
55
|
flagPoints
|
|
52
56
|
});
|
|
53
|
-
if (sentence) line = nextCopy(sentence);
|
|
57
|
+
if (sentence) line = nextCopy(next.code === 'page-no-targets' ? READINESS_COPY.choosePages : sentence);
|
|
54
58
|
}
|
|
55
59
|
if (test.status === 'running') line = runningLine(number(summary?.days), number(test.analysis?.plannedDurationDays), number(summary?.exposures));
|
|
56
60
|
if (test.status === 'decided') {
|