@delmaredigital/payload-experiments 0.3.0-rc.1 → 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 +172 -21
- 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 +47 -26
- package/dist/admin/TestsNavLink.d.ts +3 -1
- package/dist/admin/TestsNavLink.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/armSelect/ArmSelectField.js +14 -22
- package/dist/admin/arms/arms.model.d.ts +1 -21
- package/dist/admin/arms/arms.model.js +1 -153
- 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/arms.d.ts +1 -6
- package/dist/admin/copy/arms.js +1 -16
- 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 +25 -3
- package/dist/admin/copy/readiness.js +25 -3
- package/dist/admin/copy/results.d.ts +3 -0
- package/dist/admin/copy/results.js +4 -1
- package/dist/admin/copy/review.d.ts +1 -1
- package/dist/admin/copy/review.js +1 -1
- package/dist/admin/copy/surface.d.ts +0 -1
- package/dist/admin/copy/surface.js +0 -1
- 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 +8 -1
- package/dist/admin/paths.js +8 -1
- 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/previewLinks.model.d.ts +2 -0
- package/dist/admin/previewLinks.model.js +3 -0
- package/dist/admin/results/DataQualityPanel.js +4 -1
- package/dist/admin/results/legacy.d.ts +16 -0
- package/dist/admin/results/legacy.js +27 -0
- package/dist/admin/results/results.model.d.ts +9 -6
- package/dist/admin/results/results.model.js +22 -9
- package/dist/admin/results/snapshot.d.ts +14 -0
- package/dist/admin/results/snapshot.js +3 -0
- package/dist/admin/surface/TestsHome.js +43 -15
- 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 +19 -16
- 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 +2 -1
- package/dist/admin/surface/home.model.js +8 -4
- package/dist/admin/surface/newTest/NewTest.js +58 -17
- 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 +103 -69
- 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 +95 -6
- 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 +23 -3
- package/dist/admin/surface/results/ResultsHeader.js +2 -5
- package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
- package/dist/admin/surface/results/recommendation.model.js +10 -3
- package/dist/admin/surface/results/useResults.d.ts +10 -1
- package/dist/admin/surface/surface.css +0 -1
- package/dist/admin/surface/test/AdvancedSection.js +111 -104
- package/dist/admin/surface/test/GoalSection.js +97 -22
- 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/test/testPage.model.d.ts +2 -3
- package/dist/admin/surface/test/testPage.model.js +1 -21
- 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 +55 -16
- package/dist/admin/useApi.d.ts +11 -2
- package/dist/admin/useApi.js +3 -5
- package/dist/admin/useLayerUsage.js +14 -23
- package/dist/admin/views/ExperimentsRootView.js +58 -16
- package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
- package/dist/admin/views/TestsNavLinkServer.js +43 -0
- package/dist/arms/copy.d.ts +6 -0
- package/dist/arms/copy.js +16 -0
- package/dist/arms/model.d.ts +23 -0
- package/dist/arms/model.js +173 -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 +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/appends.d.ts +6 -2
- package/dist/db/appends.js +10 -4
- 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 +14 -0
- package/dist/db/locks.js +36 -1
- 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/pageOccupancy.d.ts +12 -0
- package/dist/db/pageOccupancy.js +45 -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 +9 -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 +1 -0
- package/dist/exports/adminRsc.js +1 -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 +5 -2
- package/dist/exports/puck.js +3 -1
- package/dist/exports/puckEditor.d.ts +6 -1
- package/dist/exports/puckEditor.js +4 -1
- package/dist/exports/rsc.d.ts +4 -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 +160 -80
- package/dist/lib/fetchAllDocs.d.ts +16 -0
- package/dist/lib/fetchAllDocs.js +56 -0
- package/dist/onInit.js +29 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.js +43 -10
- package/dist/plugin.js +45 -28
- package/dist/puck/ExperimentPicker.js +47 -16
- package/dist/puck/ExperimentSlot.d.ts +51 -1
- package/dist/puck/ExperimentSlot.js +23 -12
- package/dist/puck/copy.d.ts +6 -4
- package/dist/puck/copy.js +6 -4
- package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
- package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
- package/dist/puck/editor/ResultsReadout.d.ts +6 -0
- package/dist/puck/editor/ResultsReadout.js +99 -0
- package/dist/puck/editor/VersionBuilder.d.ts +34 -0
- package/dist/puck/editor/VersionBuilder.js +317 -0
- package/dist/puck/editor/armWrites.d.ts +7 -0
- package/dist/puck/editor/armWrites.js +71 -0
- package/dist/puck/editor/armsCache.d.ts +14 -0
- package/dist/puck/editor/armsCache.js +78 -0
- package/dist/puck/editor/copy.d.ts +33 -1
- package/dist/puck/editor/copy.js +45 -8
- package/dist/puck/editor/createTest.d.ts +1 -1
- package/dist/puck/editor/createTest.js +25 -14
- package/dist/puck/editor/editor.chrome.css +48 -161
- package/dist/puck/editor/editor.css +48 -161
- package/dist/puck/editor/editor.model.d.ts +8 -3
- package/dist/puck/editor/editor.model.js +16 -10
- package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
- package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
- 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/experimentSlotField.d.ts +2 -0
- package/dist/puck/experimentSlotField.js +11 -3
- package/dist/puck/hostContext.d.ts +10 -0
- package/dist/puck/hostContext.js +12 -0
- package/dist/puck/integration.d.ts +6 -0
- package/dist/puck/integration.js +21 -0
- package/dist/puck/routes.d.ts +12 -0
- package/dist/puck/routes.js +11 -0
- package/dist/puck/slotConfig.d.ts +1 -1
- package/dist/puck/slotConfig.js +2 -2
- 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 +51 -1
- package/dist/server/metadata.js +9 -2
- package/dist/server/normaliseContentRelationships.d.ts +7 -0
- package/dist/server/normaliseContentRelationships.js +62 -0
- package/dist/server/pageKindSeoContract.d.ts +27 -0
- package/dist/server/pageKindSeoContract.js +80 -0
- package/dist/server/recordContentChange.d.ts +1 -1
- package/dist/server/recordContentChange.js +72 -13
- package/dist/server/resolveExperimentBlocks.d.ts +5 -0
- package/dist/server/resolveExperimentBlocks.js +54 -0
- package/dist/server/runResults.js +92 -19
- 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 +17 -1
- package/dist/transitions/checkStart.js +99 -7
- package/dist/transitions/decide.js +40 -1
- 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 +10 -6
- package/dist/transitions/placements.js +156 -2
- package/dist/transitions/preflightData.d.ts +8 -0
- package/dist/transitions/preflightData.js +248 -36
- package/dist/transitions/resolveCaps.d.ts +1 -0
- package/dist/transitions/resolveCaps.js +6 -2
- package/dist/transitions/start.js +77 -3
- package/dist/types/admin.d.ts +1 -1
- package/dist/types/assignments.d.ts +1 -0
- package/dist/types/contracts.d.ts +31 -10
- 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/Tabs.d.ts +4 -2
- package/dist/ui/Tabs.js +33 -26
- package/dist/ui/accent.d.ts +22 -3
- package/dist/ui/accent.js +74 -16
- package/package.json +12 -2
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { DocsResponseError, fetchAllDocs } from '../../../lib/fetchAllDocs.js';
|
|
4
|
+
import { usePlacementLabels, placementDetailKey } from '../../placements/usePlacementLabels.js';
|
|
5
|
+
import { placementUncheckedCopy } from '../../copy/readiness.js';
|
|
3
6
|
import { useEffect, useState } from 'react';
|
|
4
7
|
import { Button } from '../../../ui/Button.js';
|
|
5
8
|
import { TextInput } from '../../../ui/Inputs.js';
|
|
6
9
|
import { Dialog } from '../../../ui/Dialog.js';
|
|
7
10
|
import { MakeContentResult } from '../../actions/MakeContentResult.js';
|
|
8
11
|
import { expectedConfirm, matchesConfirm } from '../../../transitions/confirmPhrases.js';
|
|
9
|
-
import { ACTIONS_COPY as A } from '../../copy/actions.js';
|
|
12
|
+
import { ACTIONS_COPY as A, addMetricWarningCopy } from '../../copy/actions.js';
|
|
10
13
|
import { REVIEW_COPY as R } from '../../copy/review.js';
|
|
11
14
|
import { READINESS_COPY as C } from '../../copy/readiness.js';
|
|
12
15
|
import { API_MESSAGES, refusalMessage } from '../../copy/common.js';
|
|
@@ -186,22 +189,26 @@ import { useTransition } from './useTransition.js';
|
|
|
186
189
|
const [loading, setLoading] = useState(true);
|
|
187
190
|
const [loadError, setLoadError] = useState(null);
|
|
188
191
|
const [metric, setMetric] = useState('');
|
|
192
|
+
const [warnings, setWarnings] = useState(null);
|
|
193
|
+
const placementLabels = usePlacementLabels({
|
|
194
|
+
details: (warnings ?? []).filter((warning)=>warning.code === 'placement-unchecked').map((warning)=>warning.detail),
|
|
195
|
+
preflight: test.preflight,
|
|
196
|
+
...surface
|
|
197
|
+
});
|
|
189
198
|
const [role, setRole] = useState('secondary');
|
|
190
199
|
const transition = useTransition(test, onClose);
|
|
191
200
|
useEffect(()=>{
|
|
192
201
|
const controller = new AbortController();
|
|
193
202
|
void (async ()=>{
|
|
194
203
|
try {
|
|
195
|
-
const response = await
|
|
204
|
+
const response = await fetchAllDocs(`${surface.apiRoute.replace(/\/$/, '')}/xp-metrics?depth=0`, {
|
|
196
205
|
credentials: 'include',
|
|
197
206
|
signal: controller.signal
|
|
198
207
|
});
|
|
199
208
|
if (!response.ok) throw new Error(refusalMessage(undefined, response.status));
|
|
200
|
-
|
|
201
|
-
if (!Array.isArray(data.docs)) throw new Error(API_MESSAGES.unreadable);
|
|
202
|
-
if (!controller.signal.aborted) setMetrics(data.docs);
|
|
209
|
+
if (!controller.signal.aborted) setMetrics(response.docs);
|
|
203
210
|
} catch (cause) {
|
|
204
|
-
if (!controller.signal.aborted) setLoadError(cause instanceof Error ? cause.message : API_MESSAGES.network);
|
|
211
|
+
if (!controller.signal.aborted) setLoadError(cause instanceof DocsResponseError ? API_MESSAGES.unreadable : cause instanceof Error ? cause.message : API_MESSAGES.network);
|
|
205
212
|
} finally{
|
|
206
213
|
if (!controller.signal.aborted) setLoading(false);
|
|
207
214
|
}
|
|
@@ -218,85 +225,112 @@ import { useTransition } from './useTransition.js';
|
|
|
218
225
|
const available = metrics.filter((item)=>!attached.has(String(item.id)));
|
|
219
226
|
const selected = available.find((item)=>String(item.id) === metric);
|
|
220
227
|
const disabled = transition.busy || test.hasUnsaved || test.saving === 'saving';
|
|
228
|
+
function close() {
|
|
229
|
+
if (transition.busy) return;
|
|
230
|
+
if (warnings) void test.reload();
|
|
231
|
+
onClose();
|
|
232
|
+
}
|
|
221
233
|
return /*#__PURE__*/ _jsxs(Dialog, {
|
|
222
234
|
open: true,
|
|
223
235
|
dismissible: !transition.busy,
|
|
224
236
|
title: C.addGoal,
|
|
225
|
-
onClose:
|
|
226
|
-
if (!transition.busy) onClose();
|
|
227
|
-
},
|
|
237
|
+
onClose: close,
|
|
228
238
|
children: [
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
239
|
+
warnings ? warnings.map((warning, index)=>{
|
|
240
|
+
const placement = warning.code === 'placement-unchecked' ? placementLabels.get(placementDetailKey(warning.detail)) : undefined;
|
|
241
|
+
return /*#__PURE__*/ _jsx("p", {
|
|
242
|
+
children: placement ? placement.href ? /*#__PURE__*/ _jsx("a", {
|
|
243
|
+
href: placement.href,
|
|
244
|
+
children: placementUncheckedCopy(placement.title)
|
|
245
|
+
}) : placementUncheckedCopy(placement.title) : addMetricWarningCopy(warning)
|
|
246
|
+
}, index);
|
|
247
|
+
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
237
248
|
children: [
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
/*#__PURE__*/ _jsx("p", {
|
|
250
|
+
children: C.addMetric
|
|
251
|
+
}),
|
|
252
|
+
loading ? /*#__PURE__*/ _jsx("p", {
|
|
253
|
+
role: "status",
|
|
254
|
+
children: R.loadingMetrics
|
|
255
|
+
}) : null,
|
|
256
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
243
257
|
children: [
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
258
|
+
R.role,
|
|
259
|
+
/*#__PURE__*/ _jsxs("select", {
|
|
260
|
+
value: role,
|
|
261
|
+
disabled: disabled,
|
|
262
|
+
onChange: (event)=>setRole(event.target.value),
|
|
263
|
+
children: [
|
|
264
|
+
/*#__PURE__*/ _jsx("option", {
|
|
265
|
+
value: "secondary",
|
|
266
|
+
children: R.supporting
|
|
267
|
+
}),
|
|
268
|
+
/*#__PURE__*/ _jsx("option", {
|
|
269
|
+
value: "guardrail",
|
|
270
|
+
children: R.guardrail
|
|
271
|
+
})
|
|
272
|
+
]
|
|
251
273
|
})
|
|
252
274
|
]
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
}),
|
|
256
|
-
/*#__PURE__*/ _jsxs("label", {
|
|
257
|
-
children: [
|
|
258
|
-
R.metric,
|
|
259
|
-
/*#__PURE__*/ _jsxs("select", {
|
|
260
|
-
value: metric,
|
|
261
|
-
disabled: disabled || loading,
|
|
262
|
-
onChange: (event)=>setMetric(event.target.value),
|
|
275
|
+
}),
|
|
276
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
263
277
|
children: [
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
278
|
+
R.metric,
|
|
279
|
+
/*#__PURE__*/ _jsxs("select", {
|
|
280
|
+
value: metric,
|
|
281
|
+
disabled: disabled || loading,
|
|
282
|
+
onChange: (event)=>setMetric(event.target.value),
|
|
283
|
+
children: [
|
|
284
|
+
/*#__PURE__*/ _jsx("option", {
|
|
285
|
+
value: "",
|
|
286
|
+
children: R.chooseMetric
|
|
287
|
+
}),
|
|
288
|
+
available.map((item)=>/*#__PURE__*/ _jsx("option", {
|
|
289
|
+
value: String(item.id),
|
|
290
|
+
children: item.name
|
|
291
|
+
}, item.id))
|
|
292
|
+
]
|
|
293
|
+
})
|
|
272
294
|
]
|
|
295
|
+
}),
|
|
296
|
+
!loading && !loadError && !available.length ? /*#__PURE__*/ _jsx("p", {
|
|
297
|
+
children: R.noMetrics
|
|
298
|
+
}) : null,
|
|
299
|
+
loadError || transition.error ? /*#__PURE__*/ _jsx("p", {
|
|
300
|
+
role: "alert",
|
|
301
|
+
children: loadError ?? transition.error
|
|
302
|
+
}) : null,
|
|
303
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
304
|
+
disabled: disabled || loading || !!loadError || !selected,
|
|
305
|
+
onClick: ()=>{
|
|
306
|
+
// Goal and phase lookups share not-found; prefer the more likely missing goal without reading server text.
|
|
307
|
+
if (selected && test.doc) void transition.run(async ()=>{
|
|
308
|
+
const result = await createApi(surface.apiRoute).addMetric(test.doc.id, {
|
|
309
|
+
metric: selected.id,
|
|
310
|
+
role
|
|
311
|
+
}, {
|
|
312
|
+
notFound: API_MESSAGES.goalNotFound
|
|
313
|
+
});
|
|
314
|
+
if (result.ok) {
|
|
315
|
+
if (result.data?.warnings?.length) setWarnings(result.data.warnings);
|
|
316
|
+
else {
|
|
317
|
+
await test.reload();
|
|
318
|
+
onClose();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return result;
|
|
322
|
+
}, {
|
|
323
|
+
reloadOnSuccess: false
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
children: C.addGoal
|
|
273
327
|
})
|
|
274
328
|
]
|
|
275
329
|
}),
|
|
276
|
-
!loading && !loadError && !available.length ? /*#__PURE__*/ _jsx("p", {
|
|
277
|
-
children: R.noMetrics
|
|
278
|
-
}) : null,
|
|
279
|
-
loadError || transition.error ? /*#__PURE__*/ _jsx("p", {
|
|
280
|
-
role: "alert",
|
|
281
|
-
children: loadError ?? transition.error
|
|
282
|
-
}) : null,
|
|
283
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
284
|
-
disabled: disabled || loading || !!loadError || !selected,
|
|
285
|
-
onClick: ()=>{
|
|
286
|
-
// Goal and phase lookups share not-found; prefer the more likely missing goal without reading server text.
|
|
287
|
-
if (selected && test.doc) void transition.run(()=>createApi(surface.apiRoute).addMetric(test.doc.id, {
|
|
288
|
-
metric: selected.id,
|
|
289
|
-
role
|
|
290
|
-
}, {
|
|
291
|
-
notFound: API_MESSAGES.goalNotFound
|
|
292
|
-
}));
|
|
293
|
-
},
|
|
294
|
-
children: C.addGoal
|
|
295
|
-
}),
|
|
296
330
|
/*#__PURE__*/ _jsx(Button, {
|
|
297
331
|
disabled: transition.busy,
|
|
298
|
-
onClick:
|
|
299
|
-
children: A.cancel
|
|
332
|
+
onClick: close,
|
|
333
|
+
children: warnings ? A.done : A.cancel
|
|
300
334
|
})
|
|
301
335
|
]
|
|
302
336
|
});
|
|
@@ -7,8 +7,10 @@ import { jumpToSection } from '../test/jumpToSection.js';
|
|
|
7
7
|
import { actionGroupLabel, availableActions } from '../../actions/actions.model.js';
|
|
8
8
|
import { createApi } from '../../api.js';
|
|
9
9
|
import { READINESS_COPY as C, decidedCopy, nextCopy, runningCopy, stoppedCopy } from '../../copy/readiness.js';
|
|
10
|
-
import { READINESS, daysRemaining, displayDate, nextStep } from './readiness.model.js';
|
|
10
|
+
import { READINESS, daysRemaining, displayDate, nextStep, pageReadinessContext } from './readiness.model.js';
|
|
11
11
|
import { AddMetricAction, AllocateAction, ConfirmAction } from './ActionDialogs.js';
|
|
12
|
+
import { preflightPlacementLabel } from '../../placements/placementLabels.js';
|
|
13
|
+
import { placementUncheckedCopy } from '../../copy/readiness.js';
|
|
12
14
|
import { useTransition } from './useTransition.js';
|
|
13
15
|
const RUNNING_ACTIONS = [
|
|
14
16
|
'stop',
|
|
@@ -49,10 +51,17 @@ export function ReadinessContent({ test, surface, resultsVersion, bar = false })
|
|
|
49
51
|
]);
|
|
50
52
|
const decision = doc.decision;
|
|
51
53
|
const winner = actionGroupLabel(decision?.winnerArm ?? 'control', doc.arms.find((item)=>item.key === decision?.winnerArm)?.label);
|
|
52
|
-
const
|
|
53
|
-
...
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const labelledPreflight = test.preflight ? {
|
|
55
|
+
...test.preflight,
|
|
56
|
+
checks: test.preflight.checks.map((check)=>check.code === 'placement-unchecked' ? {
|
|
57
|
+
...check,
|
|
58
|
+
detail: {
|
|
59
|
+
...check.detail,
|
|
60
|
+
title: preflightPlacementLabel(check.detail, test.preflight, surface.adminRoute).title
|
|
61
|
+
}
|
|
62
|
+
} : check)
|
|
63
|
+
} : null;
|
|
64
|
+
const next = nextStep(labelledPreflight, pageReadinessContext(doc, test.preflight, surface));
|
|
56
65
|
const setup = doc.status === 'draft' || doc.status === 'stopped';
|
|
57
66
|
const ready = Boolean(test.preflight && !test.preflightPending && !test.preflightError && test.preflight.checks.every((check)=>check.ok));
|
|
58
67
|
const unsaved = test.hasUnsaved || test.saving === 'saving';
|
|
@@ -96,7 +105,7 @@ export function ReadinessContent({ test, surface, resultsVersion, bar = false })
|
|
|
96
105
|
href: `#section-${next.section}`,
|
|
97
106
|
onClick: (event)=>{
|
|
98
107
|
event.preventDefault();
|
|
99
|
-
if (next.section !== 'header') jumpToSection(next.section);
|
|
108
|
+
if (next.section !== 'header') jumpToSection(next.section, next.arm);
|
|
100
109
|
},
|
|
101
110
|
children: [
|
|
102
111
|
sentence,
|
|
@@ -119,6 +128,21 @@ export function ReadinessContent({ test, surface, resultsVersion, bar = false })
|
|
|
119
128
|
test.preflight?.checks.filter((check)=>check.code === 'make-content-unsupported' && check.warning).map((check)=>/*#__PURE__*/ _jsx("p", {
|
|
120
129
|
children: READINESS[check.code].next(check.detail, doc)
|
|
121
130
|
}, check.code)),
|
|
131
|
+
test.preflight?.checks.filter((check)=>check.warning && [
|
|
132
|
+
'goal-in-some-versions',
|
|
133
|
+
'placement-unchecked'
|
|
134
|
+
].includes(check.code)).map((check, index)=>{
|
|
135
|
+
const placement = check.code === 'placement-unchecked' ? preflightPlacementLabel(check.detail, test.preflight, surface.adminRoute) : undefined;
|
|
136
|
+
return /*#__PURE__*/ _jsx("p", {
|
|
137
|
+
children: placement ? placement.href ? /*#__PURE__*/ _jsx("a", {
|
|
138
|
+
href: placement.href,
|
|
139
|
+
children: placementUncheckedCopy(placement.title)
|
|
140
|
+
}) : placementUncheckedCopy(placement.title) : READINESS[check.code].next(check.detail, doc)
|
|
141
|
+
}, `${check.code}:${index}`);
|
|
142
|
+
}),
|
|
143
|
+
test.preflight?.goalNotes?.map((note)=>/*#__PURE__*/ _jsx("p", {
|
|
144
|
+
children: note.message
|
|
145
|
+
}, note.metricId)),
|
|
122
146
|
doc.status === 'stopped' ? /*#__PURE__*/ _jsx("p", {
|
|
123
147
|
children: stoppedCopy(displayDate(doc.stoppedAt))
|
|
124
148
|
}) : null,
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import type { ReadinessCode, PreflightResult } from '../../../types/endpoints.js';
|
|
2
2
|
import type { SerializedFlagPoint } from '../../../types/admin.js';
|
|
3
3
|
import type { TestDocument } from '../testDocument.model.js';
|
|
4
|
+
import type { SurfaceProps } from '../contracts.js';
|
|
4
5
|
import { type SectionId } from '../contracts.js';
|
|
5
6
|
export type ReadinessContext = Pick<TestDocument, 'arms' | 'unitType' | 'flagPoint'> & {
|
|
7
|
+
collectionLabel?: string;
|
|
8
|
+
pagePlacements?: PreflightResult['pagePlacements'];
|
|
6
9
|
flagPoints?: readonly Pick<SerializedFlagPoint, 'id' | 'params'>[];
|
|
7
10
|
};
|
|
8
11
|
/** Exhaustive mapping of the checkStart ReadinessCode union, including all ten invariant checks. */
|
|
9
12
|
export declare const READINESS: Record<ReadinessCode, {
|
|
10
13
|
section: SectionId | 'header';
|
|
11
14
|
next(detail: unknown, ctx: ReadinessContext): string | null;
|
|
12
|
-
nextStep?: string;
|
|
15
|
+
nextStep?: string | ((detail: unknown, ctx: ReadinessContext) => string);
|
|
13
16
|
}>;
|
|
14
17
|
export declare function nextStep(preflight: PreflightResult | null, ctx: ReadinessContext): {
|
|
15
18
|
nextStep?: string | undefined;
|
|
19
|
+
arm?: import("../../../types/contracts.js").ArmKey | undefined;
|
|
16
20
|
section: "header" | SectionId;
|
|
17
21
|
sentence: string;
|
|
18
22
|
code: ReadinessCode;
|
|
@@ -22,3 +26,6 @@ export declare function sectionDone(test: {
|
|
|
22
26
|
}): Record<SectionId, boolean>;
|
|
23
27
|
export declare function displayDate(value: unknown): string | null;
|
|
24
28
|
export declare function daysRemaining(doc: TestDocument, now: number): number | null;
|
|
29
|
+
export declare function pageReadinessContext(doc: TestDocument, preflight: PreflightResult | null, surface: SurfaceProps): ReadinessContext;
|
|
30
|
+
/** First affected row in arm order; shared by the wizard and the test-page jump. */
|
|
31
|
+
export declare function readinessArm(code: ReadinessCode, detail: unknown, ctx: ReadinessContext): import("../../../types/contracts.js").ArmKey | undefined;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { SURFACE_COPY } from '../../copy/surface.js';
|
|
1
2
|
import { formatDate } from '../../format/date.js';
|
|
2
3
|
import { actionGroupLabel } from '../../actions/actions.model.js';
|
|
3
|
-
import { unsupportedContentCopy, versionPageUnavailableCopy, versionPageUncheckedCopy, placementFailureCopy, unpublishedPageCopy, READINESS_COPY as C, attributeNext, contentNext, goalPeopleNext, parameterNext, placementNext, ruleNext, versionsNext } from '../../copy/readiness.js';
|
|
4
|
+
import { asymmetricGoalsCopy, placementUncheckedCopy, interactingTestsCopy, sameAddressCopy, identicalVersionCopy, unsupportedContentCopy, versionPageUnavailableCopy, versionPageUncheckedCopy, placementFailureCopy, pageInUseCopy, unpublishedPageCopy, READINESS_COPY as C, attributeNext, contentNext, goalPeopleNext, parameterNext, placementNext, ruleNext, versionsNext } from '../../copy/readiness.js';
|
|
5
|
+
import { normalisePath } from '../../../runtime/normalisePath.js';
|
|
6
|
+
import { testPageCollection } from '../pagePicker/pagePicker.model.js';
|
|
4
7
|
import { SECTION_ORDER } from '../contracts.js';
|
|
5
8
|
const object = (value)=>value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
6
9
|
const first = (value)=>object(Array.isArray(value) ? value[0] : value);
|
|
@@ -17,8 +20,30 @@ const row = (section, next)=>({
|
|
|
17
20
|
const versions = row('versions', (detail)=>versionsNext(detailText(detail, C.versionFallback)));
|
|
18
21
|
/** Exhaustive mapping of the checkStart ReadinessCode union, including all ten invariant checks. */ export const READINESS = {
|
|
19
22
|
'make-content-unsupported': row('header', (detail)=>unsupportedContentCopy(text(detail, 'This page'))),
|
|
23
|
+
'goal-in-some-versions': {
|
|
24
|
+
...row('win', (detail)=>asymmetricGoalsCopy(detail)),
|
|
25
|
+
nextStep: 'Edit the versions'
|
|
26
|
+
},
|
|
27
|
+
'placement-unchecked': {
|
|
28
|
+
...row('versions', (detail)=>placementUncheckedCopy(text(object(detail).title ?? object(detail).path, 'This page'))),
|
|
29
|
+
nextStep: (detail)=>`Open ${text(object(detail).title ?? object(detail).path, 'the page')}`
|
|
30
|
+
},
|
|
20
31
|
'invalid-status': row('header', ()=>null),
|
|
21
32
|
'no-placements': row('what', (detail)=>detail === undefined ? C.noPlacements : placementFailureCopy(detail)),
|
|
33
|
+
'placement-unpublished': {
|
|
34
|
+
...row('versions', (detail)=>(Array.isArray(detail) && detail.length ? detail : [
|
|
35
|
+
detail
|
|
36
|
+
]).map((placement)=>`The block you're testing is only on a draft of ${text(object(placement).title, 'this page')}. Publish the page before you start.`).join('\n')),
|
|
37
|
+
nextStep: 'Publish the page'
|
|
38
|
+
},
|
|
39
|
+
'duplicate-version-row': {
|
|
40
|
+
...row('versions', (detail)=>`The Experiment block on ${text(first(detail).title, 'this page')} has the same version twice. Remove one of them, then save.`),
|
|
41
|
+
nextStep: (detail)=>`Edit ${text(first(detail).title, 'this page')}`
|
|
42
|
+
},
|
|
43
|
+
'interacting-tests': {
|
|
44
|
+
...row('versions', (detail)=>interactingTestsCopy(text(first(detail).path, 'this page'), text(first(detail).status, 'running'), text(first(detail).name, 'another test'), first(detail).version === true)),
|
|
45
|
+
nextStep: (detail)=>`Open ${text(first(detail).name, 'the test')}${first(detail).version === true ? `\nEdit ${text(first(detail).path, 'this page')}` : ''}`
|
|
46
|
+
},
|
|
22
47
|
'empty-slot': row('versions', (detail, ctx)=>{
|
|
23
48
|
const placements = Array.isArray(detail) ? detail : [];
|
|
24
49
|
const missing = ctx.arms.find((item)=>item.kind !== 'holdout' && placements.some((placement)=>{
|
|
@@ -49,8 +74,40 @@ const versions = row('versions', (detail)=>versionsNext(detailText(detail, C.ver
|
|
|
49
74
|
'no-reach-per-day': row('length', ()=>C.noReach),
|
|
50
75
|
'layer-full': row('versions', ()=>C.full),
|
|
51
76
|
'layer-unit-mismatch': row('header', ()=>C.differentPeople),
|
|
52
|
-
'page-
|
|
53
|
-
|
|
77
|
+
'page-in-use': {
|
|
78
|
+
...row('versions', (detail, ctx)=>{
|
|
79
|
+
const paths = object(detail).paths;
|
|
80
|
+
const path = text(Array.isArray(paths) ? paths[0] : undefined, 'this page');
|
|
81
|
+
return pageInUseCopy(path, pageArm(path, ctx)?.kind === 'variant');
|
|
82
|
+
}),
|
|
83
|
+
nextStep: C.choosePages
|
|
84
|
+
},
|
|
85
|
+
'page-no-targets': {
|
|
86
|
+
...row('versions', ()=>C.noTargets),
|
|
87
|
+
nextStep: 'Choose pages'
|
|
88
|
+
},
|
|
89
|
+
'unknown-collection': row('versions', ()=>"That page type isn't set up for tests."),
|
|
90
|
+
'version-identical': {
|
|
91
|
+
...row('versions', (detail, ctx)=>identicalVersionCopy(text(object(detail).path, object(detail).title ? `${group(object(detail).arm, ctx)} on ${text(object(detail).title, 'this page')}` : group(object(detail).arm, ctx)))),
|
|
92
|
+
nextStep: (detail, ctx)=>`Edit ${text(object(detail).path, group(object(detail).arm, ctx))}`
|
|
93
|
+
},
|
|
94
|
+
'version-same-address': {
|
|
95
|
+
...row('versions', (detail, ctx)=>sameAddressCopy(group(object(detail).arm, ctx), group(object(detail).otherArm, ctx), text(object(detail).path, 'this address'), ctx.collectionLabel)),
|
|
96
|
+
nextStep: 'Choose pages'
|
|
97
|
+
},
|
|
98
|
+
'page-target-unpublished': {
|
|
99
|
+
...row('versions', (detail, ctx)=>{
|
|
100
|
+
const path = text(typeof detail === 'string' ? detail : object(detail).path, 'This page');
|
|
101
|
+
const arm = ctx.arms.find((item)=>item.key === object(detail).arm) ?? pageArm(path, ctx);
|
|
102
|
+
const placement = (arm ? ctx.pagePlacements?.find((entry)=>entry.arm === arm.key) : undefined) ?? ctx.pagePlacements?.find((entry)=>normalisePath(entry.path) === normalisePath(path));
|
|
103
|
+
return unpublishedPageCopy(path, ctx.pagePlacements && ctx.collectionLabel ? {
|
|
104
|
+
original: arm?.kind === 'control',
|
|
105
|
+
found: placement?.id != null && placement.published === false,
|
|
106
|
+
collection: ctx.collectionLabel
|
|
107
|
+
} : undefined);
|
|
108
|
+
}),
|
|
109
|
+
nextStep: C.choosePages
|
|
110
|
+
},
|
|
54
111
|
'page-targets-unchecked': row('versions', ()=>C.uncheckedPages),
|
|
55
112
|
'version-page-unavailable': row('versions', (detail)=>versionPageUnavailableCopy(text(detail, 'This page'))),
|
|
56
113
|
'version-page-unchecked': row('versions', (detail)=>versionPageUncheckedCopy(text(detail, 'This page'))),
|
|
@@ -84,15 +141,28 @@ export function nextStep(preflight, ctx) {
|
|
|
84
141
|
...SECTION_ORDER
|
|
85
142
|
]){
|
|
86
143
|
for (const check of preflight?.checks ?? []){
|
|
144
|
+
if (!Object.hasOwn(READINESS, check.code)) {
|
|
145
|
+
if (!check.ok && section === SECTION_ORDER[0]) return {
|
|
146
|
+
section,
|
|
147
|
+
sentence: SURFACE_COPY.next,
|
|
148
|
+
code: check.code
|
|
149
|
+
};
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
87
152
|
const mapping = READINESS[check.code];
|
|
88
153
|
if (check.ok || mapping.section !== section) continue;
|
|
89
154
|
const sentence = mapping.next(check.detail, ctx);
|
|
155
|
+
const nextStep = typeof mapping.nextStep === 'function' ? mapping.nextStep(check.detail, ctx) : mapping.nextStep;
|
|
156
|
+
const arm = readinessArm(check.code, check.detail, ctx);
|
|
90
157
|
if (sentence) return {
|
|
91
158
|
section,
|
|
92
159
|
sentence,
|
|
93
160
|
code: check.code,
|
|
94
|
-
...
|
|
95
|
-
|
|
161
|
+
...arm ? {
|
|
162
|
+
arm
|
|
163
|
+
} : {},
|
|
164
|
+
...nextStep ? {
|
|
165
|
+
nextStep
|
|
96
166
|
} : {}
|
|
97
167
|
};
|
|
98
168
|
}
|
|
@@ -102,7 +172,7 @@ export function nextStep(preflight, ctx) {
|
|
|
102
172
|
export function sectionDone(test) {
|
|
103
173
|
return Object.fromEntries(SECTION_ORDER.map((section)=>[
|
|
104
174
|
section,
|
|
105
|
-
test.preflight ? !test.preflight.checks.some((check)=>!check.ok && READINESS[check.code].section === section) : section === 'who' || section === 'advanced'
|
|
175
|
+
test.preflight ? !test.preflight.checks.some((check)=>!check.ok && (Object.hasOwn(READINESS, check.code) ? READINESS[check.code].section : SECTION_ORDER[0]) === section) : section === 'who' || section === 'advanced'
|
|
106
176
|
]));
|
|
107
177
|
}
|
|
108
178
|
export function displayDate(value) {
|
|
@@ -114,3 +184,22 @@ export function daysRemaining(doc, now) {
|
|
|
114
184
|
const start = typeof doc.startedAt === 'string' ? Date.parse(doc.startedAt) : NaN;
|
|
115
185
|
return typeof days === 'number' && days > 0 && Number.isFinite(start) ? Math.max(0, Math.ceil(days - (now - start) / 86_400_000)) : null;
|
|
116
186
|
}
|
|
187
|
+
function pageArm(path, ctx) {
|
|
188
|
+
return ctx.arms.find((arm)=>arm.pageTarget && normalisePath(arm.pageTarget) === normalisePath(path));
|
|
189
|
+
}
|
|
190
|
+
export function pageReadinessContext(doc, preflight, surface) {
|
|
191
|
+
return {
|
|
192
|
+
...doc,
|
|
193
|
+
flagPoints: surface.flagPoints,
|
|
194
|
+
pagePlacements: preflight?.pagePlacements,
|
|
195
|
+
collectionLabel: testPageCollection(surface.pageCollections, doc, preflight?.pagePlacements)?.label
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/** First affected row in arm order; shared by the wizard and the test-page jump. */ export function readinessArm(code, detail, ctx) {
|
|
199
|
+
if (code === 'version-same-address') return ctx.arms.find((arm)=>arm.key === object(detail).arm)?.key;
|
|
200
|
+
if (code === 'page-no-targets') return ctx.arms.find((arm)=>arm.kind !== 'holdout' && !arm.pageTarget)?.key;
|
|
201
|
+
const paths = code === 'page-in-use' ? object(detail).paths : code === 'page-target-unpublished' ? [
|
|
202
|
+
detail
|
|
203
|
+
] : [];
|
|
204
|
+
return ctx.arms.find((arm)=>arm.kind !== 'holdout' && arm.pageTarget && Array.isArray(paths) && paths.some((path)=>typeof path === 'string' && normalisePath(path) === normalisePath(arm.pageTarget)))?.key;
|
|
205
|
+
}
|
|
@@ -8,7 +8,9 @@ export declare function ReleasePagesDialog({ id, apiRoute, onClose, onSuccess, o
|
|
|
8
8
|
onRefusal(message: string): void;
|
|
9
9
|
}): import("react").JSX.Element;
|
|
10
10
|
/** An inaccessible or deleted author never hides the recorded release date. */
|
|
11
|
-
export declare function PagesReleased({ decision, apiRoute }: {
|
|
11
|
+
export declare function PagesReleased({ decision, apiRoute, userId, userCollection: collection }: {
|
|
12
12
|
decision: PageReleaseDecision;
|
|
13
13
|
apiRoute: string;
|
|
14
|
+
userId?: DocId;
|
|
15
|
+
userCollection?: string;
|
|
14
16
|
}): import("react").JSX.Element;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { useAuth, useConfig } from '@payloadcms/ui';
|
|
5
4
|
import { Button } from '../../../ui/Button.js';
|
|
6
5
|
import { Dialog } from '../../../ui/Dialog.js';
|
|
7
6
|
import { createApi } from '../../api.js';
|
|
@@ -63,10 +62,7 @@ export function ReleasePagesDialog({ id, apiRoute, onClose, onSuccess, onRefusal
|
|
|
63
62
|
]
|
|
64
63
|
});
|
|
65
64
|
}
|
|
66
|
-
/** An inaccessible or deleted author never hides the recorded release date. */ export function PagesReleased({ decision, apiRoute }) {
|
|
67
|
-
const { user } = useAuth();
|
|
68
|
-
const { config } = useConfig();
|
|
69
|
-
const collection = config.admin.user;
|
|
65
|
+
/** An inaccessible or deleted author never hides the recorded release date. */ export function PagesReleased({ decision, apiRoute, userId, userCollection: collection }) {
|
|
70
66
|
const by = decision.pagesReleasedBy;
|
|
71
67
|
const [resolved, setResolved] = useState(null);
|
|
72
68
|
const key = JSON.stringify([
|
|
@@ -74,7 +70,7 @@ export function ReleasePagesDialog({ id, apiRoute, onClose, onSuccess, onRefusal
|
|
|
74
70
|
collection,
|
|
75
71
|
by
|
|
76
72
|
]);
|
|
77
|
-
const own = pagesReleasedBy(by,
|
|
73
|
+
const own = pagesReleasedBy(by, userId);
|
|
78
74
|
useEffect(()=>{
|
|
79
75
|
if (!by || own || !collection) return;
|
|
80
76
|
const controller = new AbortController();
|
|
@@ -109,7 +105,7 @@ export function ReleasePagesDialog({ id, apiRoute, onClose, onSuccess, onRefusal
|
|
|
109
105
|
key,
|
|
110
106
|
own
|
|
111
107
|
]);
|
|
112
|
-
const name = own ?? pagesReleasedBy(by,
|
|
108
|
+
const name = own ?? pagesReleasedBy(by, userId, resolved?.key === key ? resolved.author : null);
|
|
113
109
|
return /*#__PURE__*/ _jsx("p", {
|
|
114
110
|
children: pagesReleasedCopy(formatDate(decision.pagesReleasedAt), name)
|
|
115
111
|
});
|
|
@@ -12,6 +12,7 @@ import { METRICS_COPY } from '../../copy/metrics.js';
|
|
|
12
12
|
import { API_MESSAGES } from '../../copy/common.js';
|
|
13
13
|
import { armLabel } from '../../labels.js';
|
|
14
14
|
import { PreviewLinks } from '../../PreviewLinks.js';
|
|
15
|
+
import { actionablePreviewPages } from '../../previewLinks.model.js';
|
|
15
16
|
import { VerdictHero } from '../../results/VerdictHero.js';
|
|
16
17
|
import { StatTile } from '../../results/StatTile.js';
|
|
17
18
|
import { DataTable, Swatch } from '../../results/DataTable.js';
|
|
@@ -26,6 +27,7 @@ import { DecideDialog } from '../../actions/DecideDialog.js';
|
|
|
26
27
|
import { Banner } from '../../../ui/Banner.js';
|
|
27
28
|
import { canReleasePages } from '../../results/releasePages.model.js';
|
|
28
29
|
import { PagesReleased, ReleasePagesDialog } from './ReleasePages.js';
|
|
30
|
+
import { usePlacementLabels } from '../../placements/usePlacementLabels.js';
|
|
29
31
|
export function ResultsBody({ id, surface, onRecomputed, onPagesReleased, renderPage }) {
|
|
30
32
|
// A different test owns an independent selection and action lifecycle.
|
|
31
33
|
return /*#__PURE__*/ _jsx(ResultsContent, {
|
|
@@ -115,12 +117,20 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
|
|
|
115
117
|
isDecidedTestRound,
|
|
116
118
|
winnerLabel: winner
|
|
117
119
|
}) : null;
|
|
120
|
+
const placementLabels = usePlacementLabels({
|
|
121
|
+
details: shown?.health?.filter((item)=>item.code === 'placement-unchecked').map((item)=>item.detail) ?? [],
|
|
122
|
+
apiRoute: surface.apiRoute,
|
|
123
|
+
adminRoute: surface.adminRoute,
|
|
124
|
+
canManage: manage,
|
|
125
|
+
collectionTitles: surface.collectionTitles
|
|
126
|
+
});
|
|
118
127
|
const context = {
|
|
119
128
|
status,
|
|
120
129
|
phase,
|
|
121
130
|
inLayer: doc?.layer != null,
|
|
122
131
|
layerSlices: doc?.layerSlices,
|
|
123
|
-
allocation: doc?.allocation
|
|
132
|
+
allocation: doc?.allocation,
|
|
133
|
+
placementLabels
|
|
124
134
|
};
|
|
125
135
|
const primary = phase?.metrics.find((metric)=>metric.role === 'primary');
|
|
126
136
|
// The endpoint decides the current round; historical rounds are read-only.
|
|
@@ -146,7 +156,9 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
|
|
|
146
156
|
// Release details come only from the manager's own document, never the results response.
|
|
147
157
|
const released = manage && doc?.decision?.pagesReleasedAt ? /*#__PURE__*/ _jsx(PagesReleased, {
|
|
148
158
|
decision: doc.decision,
|
|
149
|
-
apiRoute: surface.apiRoute
|
|
159
|
+
apiRoute: surface.apiRoute,
|
|
160
|
+
userId: surface.userId,
|
|
161
|
+
userCollection: surface.userCollection
|
|
150
162
|
}) : null;
|
|
151
163
|
const meta = /*#__PURE__*/ _jsxs(_Fragment, {
|
|
152
164
|
children: [
|
|
@@ -262,6 +274,9 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
|
|
|
262
274
|
}, context),
|
|
263
275
|
sinceShipping: rolloutLine(shown, phase, phases, decision ?? undefined)
|
|
264
276
|
}),
|
|
277
|
+
primary?.clickGoal ? /*#__PURE__*/ _jsx("p", {
|
|
278
|
+
children: C.clickPrimary
|
|
279
|
+
}) : null,
|
|
265
280
|
/*#__PURE__*/ _jsxs("div", {
|
|
266
281
|
className: "xp-stat-tiles",
|
|
267
282
|
children: [
|
|
@@ -308,6 +323,9 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
|
|
|
308
323
|
})
|
|
309
324
|
]
|
|
310
325
|
}) : null,
|
|
326
|
+
shown.goalFairnessNotice ? /*#__PURE__*/ _jsx("p", {
|
|
327
|
+
children: shown.goalFairnessNotice
|
|
328
|
+
}) : null,
|
|
311
329
|
/*#__PURE__*/ _jsx(DataQualityPanel, {
|
|
312
330
|
problem: shown.verdict.code === 'data-problem',
|
|
313
331
|
items: dataQuality(shown, context)
|
|
@@ -329,7 +347,7 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
|
|
|
329
347
|
}) : null
|
|
330
348
|
]
|
|
331
349
|
}),
|
|
332
|
-
manage && placements && placements.length > 0 ? /*#__PURE__*/ _jsxs("section", {
|
|
350
|
+
manage && placements && actionablePreviewPages(placements, doc?.flagPoint).length > 0 ? /*#__PURE__*/ _jsxs("section", {
|
|
333
351
|
className: "xp-results-block",
|
|
334
352
|
children: [
|
|
335
353
|
/*#__PURE__*/ _jsx("div", {
|
|
@@ -339,6 +357,7 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
|
|
|
339
357
|
})
|
|
340
358
|
}),
|
|
341
359
|
/*#__PURE__*/ _jsx(PreviewLinks, {
|
|
360
|
+
apiRoute: surface.apiRoute,
|
|
342
361
|
experimentId: id,
|
|
343
362
|
flagPoint: doc?.flagPoint,
|
|
344
363
|
placements: placements,
|
|
@@ -359,6 +378,7 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
|
|
|
359
378
|
]
|
|
360
379
|
}) : null,
|
|
361
380
|
deciding && manage && phase ? /*#__PURE__*/ _jsx(DecideDialog, {
|
|
381
|
+
apiRoute: surface.apiRoute,
|
|
362
382
|
id: id,
|
|
363
383
|
arms: phase.arms,
|
|
364
384
|
checks: checks,
|
|
@@ -8,7 +8,7 @@ import { Banner, StatusChip } from '../../../ui/Feedback.js';
|
|
|
8
8
|
import { createApi } from '../../api.js';
|
|
9
9
|
import { availableActions, actionGroupLabel } from '../../actions/actions.model.js';
|
|
10
10
|
import { AllocateAction, ConfirmAction } from '../readiness/ActionDialogs.js';
|
|
11
|
-
import { READINESS, daysRemaining, displayDate, nextStep } from '../readiness/readiness.model.js';
|
|
11
|
+
import { READINESS, daysRemaining, displayDate, nextStep, pageReadinessContext } from '../readiness/readiness.model.js';
|
|
12
12
|
import { READINESS_COPY as R, runningCopy, stoppedCopy, decidedCopy } from '../../copy/readiness.js';
|
|
13
13
|
import { RESULTS_COPY as C } from '../../copy/results.js';
|
|
14
14
|
import { TEST_PAGE } from '../../copy/testPage.js';
|
|
@@ -54,10 +54,7 @@ export function ResultsHeader({ test, surface, resultsVersion, children, actions
|
|
|
54
54
|
'allocate',
|
|
55
55
|
'stop'
|
|
56
56
|
].indexOf(b.id) : 0);
|
|
57
|
-
const next = nextStep(test.preflight,
|
|
58
|
-
...doc,
|
|
59
|
-
flagPoints: surface.flagPoints
|
|
60
|
-
});
|
|
57
|
+
const next = nextStep(test.preflight, pageReadinessContext(doc, test.preflight, surface));
|
|
61
58
|
const ready = Boolean(test.preflight && !test.preflightPending && !test.preflightError && test.preflight.checks.every((check)=>check.ok));
|
|
62
59
|
const startReason = next?.sentence ?? (!ready ? test.preflightError ? R.checkError : R.checking : undefined);
|
|
63
60
|
const action = available.find((item)=>item.id === selected);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { PhaseSummary } from '../../../types/endpoints.js';
|
|
2
2
|
import type { Status } from '../../../types/contracts.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { VerdictCode } from '../../../types/snapshot.js';
|
|
4
|
+
import { type DisplaySnapshot } from '../../results/snapshot.js';
|
|
4
5
|
export interface RecommendationContext {
|
|
5
|
-
snapshot:
|
|
6
|
+
snapshot: DisplaySnapshot;
|
|
6
7
|
phase?: PhaseSummary;
|
|
7
8
|
plannedDurationDays?: number | null;
|
|
8
9
|
status?: Status;
|