@delmaredigital/payload-experiments 0.2.0-rc.6 → 0.3.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 +129 -11
- package/dist/admin/PreviewLinks.d.ts +2 -1
- package/dist/admin/PreviewLinks.js +24 -16
- package/dist/admin/actions/ConfirmDialog.d.ts +7 -4
- package/dist/admin/actions/ConfirmDialog.js +37 -82
- package/dist/admin/actions/DecideDialog.d.ts +3 -1
- package/dist/admin/actions/DecideDialog.js +30 -13
- package/dist/admin/actions/MakeContentResult.d.ts +13 -2
- package/dist/admin/actions/MakeContentResult.js +115 -16
- package/dist/admin/actions/actions.model.d.ts +2 -4
- package/dist/admin/actions/actions.model.js +2 -3
- package/dist/admin/allocation.model.js +3 -5
- package/dist/admin/api.d.ts +10 -4
- package/dist/admin/api.js +11 -7
- package/dist/admin/copy/actions.d.ts +39 -3
- package/dist/admin/copy/actions.js +40 -4
- package/dist/admin/copy/armSelect.js +5 -5
- package/dist/admin/copy/arms.d.ts +1 -7
- package/dist/admin/copy/arms.js +1 -7
- package/dist/admin/copy/audience.d.ts +2 -1
- package/dist/admin/copy/audience.js +3 -2
- package/dist/admin/copy/common.d.ts +13 -6
- package/dist/admin/copy/common.js +14 -8
- package/dist/admin/copy/goals.d.ts +21 -6
- package/dist/admin/copy/goals.js +27 -6
- package/dist/admin/copy/readiness.d.ts +19 -0
- package/dist/admin/copy/readiness.js +19 -0
- package/dist/admin/copy/recommendation.d.ts +0 -3
- package/dist/admin/copy/recommendation.js +0 -3
- package/dist/admin/copy/results.d.ts +41 -2
- package/dist/admin/copy/results.js +42 -3
- package/dist/admin/copy/review.d.ts +1 -1
- package/dist/admin/copy/review.js +1 -1
- package/dist/admin/copy/setting.d.ts +2 -0
- package/dist/admin/copy/setting.js +2 -0
- package/dist/admin/copy/surface.d.ts +7 -4
- package/dist/admin/copy/surface.js +9 -4
- package/dist/admin/copy/testPage.d.ts +12 -0
- package/dist/admin/copy/testPage.js +12 -0
- package/dist/admin/format/date.d.ts +2 -0
- package/dist/admin/format/date.js +10 -0
- package/dist/admin/labels.d.ts +2 -7
- package/dist/admin/labels.js +2 -7
- package/dist/admin/previewLinks.model.d.ts +2 -2
- package/dist/admin/previewLinks.model.js +5 -7
- package/dist/admin/results/CiChart.js +11 -21
- package/dist/admin/results/DataQualityPanel.d.ts +4 -2
- package/dist/admin/results/DataQualityPanel.js +30 -17
- package/dist/admin/results/DataTable.d.ts +16 -0
- package/dist/admin/results/DataTable.js +152 -0
- package/dist/admin/results/StatTile.d.ts +7 -0
- package/dist/admin/results/StatTile.js +22 -0
- package/dist/admin/results/VerdictHero.d.ts +13 -0
- package/dist/admin/results/{VerdictCard.js → VerdictHero.js} +15 -12
- package/dist/admin/results/releasePages.model.d.ts +17 -0
- package/dist/admin/results/releasePages.model.js +10 -0
- package/dist/admin/results/results.css +64 -0
- package/dist/admin/results/results.model.d.ts +6 -2
- package/dist/admin/results/results.model.js +12 -5
- package/dist/admin/surface/ResultsRoute.d.ts +1 -1
- package/dist/admin/surface/ResultsRoute.js +60 -31
- package/dist/admin/surface/SurfaceShell.d.ts +11 -2
- package/dist/admin/surface/SurfaceShell.js +49 -14
- package/dist/admin/surface/TestsHome.d.ts +6 -1
- package/dist/admin/surface/TestsHome.js +183 -112
- package/dist/admin/surface/TestsSurface.js +56 -16
- package/dist/admin/surface/contracts.d.ts +1 -0
- package/dist/admin/surface/goals/GoalForm.js +341 -308
- package/dist/admin/surface/goals/GoalsList.js +123 -89
- package/dist/admin/surface/goals/data.d.ts +8 -2
- package/dist/admin/surface/goals/data.js +11 -3
- package/dist/admin/surface/goals/goals.css +10 -13
- package/dist/admin/surface/home.model.d.ts +7 -8
- package/dist/admin/surface/home.model.js +23 -12
- package/dist/admin/surface/newTest/NewTest.js +5 -5
- package/dist/admin/surface/pageBlock/parts.js +31 -26
- package/dist/admin/surface/readiness/ActionDialogs.js +43 -14
- package/dist/admin/surface/readiness/Banner.d.ts +3 -0
- package/dist/admin/surface/readiness/Banner.js +63 -36
- package/dist/admin/surface/readiness/SaveStatus.d.ts +1 -0
- package/dist/admin/surface/readiness/SaveStatus.js +6 -3
- package/dist/admin/surface/readiness/readiness.model.d.ts +2 -0
- package/dist/admin/surface/readiness/readiness.model.js +18 -9
- package/dist/admin/surface/readiness/useTransition.d.ts +1 -0
- package/dist/admin/surface/readiness/useTransition.js +2 -1
- package/dist/admin/surface/results/ReleasePages.d.ts +14 -0
- package/dist/admin/surface/results/ReleasePages.js +116 -0
- package/dist/admin/surface/results/ResultsBody.d.ts +15 -2
- package/dist/admin/surface/results/ResultsBody.js +281 -107
- package/dist/admin/surface/results/ResultsHeader.d.ts +10 -0
- package/dist/admin/surface/results/ResultsHeader.js +177 -0
- package/dist/admin/surface/results/recommendation.model.js +2 -6
- package/dist/admin/surface/results/useResults.d.ts +21 -3
- package/dist/admin/surface/results/useResults.js +2 -1
- package/dist/admin/surface/setting/parts.js +75 -88
- package/dist/admin/surface/setting/setting.css +3 -15
- package/dist/admin/surface/surface.css +62 -47
- package/dist/admin/surface/test/AdvancedSection.js +55 -48
- package/dist/admin/surface/test/GoalSection.js +39 -32
- package/dist/admin/surface/test/LengthSection.js +46 -34
- package/dist/admin/surface/test/Rail.d.ts +4 -0
- package/dist/admin/surface/test/Rail.js +83 -0
- package/dist/admin/surface/test/ReadinessBar.d.ts +5 -0
- package/dist/admin/surface/test/ReadinessBar.js +33 -0
- package/dist/admin/surface/test/Section.d.ts +1 -1
- package/dist/admin/surface/test/Section.js +36 -16
- package/dist/admin/surface/test/TargetingSection.js +168 -142
- package/dist/admin/surface/test/TestPage.d.ts +2 -1
- package/dist/admin/surface/test/TestPage.js +146 -59
- package/dist/admin/surface/test/VersionsSection.js +151 -120
- package/dist/admin/surface/test/fields.d.ts +11 -4
- package/dist/admin/surface/test/fields.js +84 -85
- package/dist/admin/surface/test/jumpToSection.d.ts +3 -0
- package/dist/admin/surface/test/jumpToSection.js +10 -0
- package/dist/admin/surface/test/pageParts.js +9 -3
- package/dist/admin/surface/test/testPage.css +81 -14
- package/dist/admin/surface/useTestDocument.d.ts +1 -0
- package/dist/admin/surface/useTestDocument.js +40 -9
- package/dist/admin/targeting/targeting.model.d.ts +1 -3
- package/dist/admin/targeting/targeting.model.js +3 -8
- package/dist/admin/theme.css +115 -36
- package/dist/admin/theme.d.ts +38 -0
- package/dist/admin/theme.js +38 -1
- package/dist/admin/useApi.d.ts +17 -2
- package/dist/admin/views/ExperimentsRootView.js +12 -2
- package/dist/collections/experiments/config.js +44 -1
- package/dist/collections/experiments/lockHook.js +16 -2
- package/dist/collections/identities.js +6 -1
- package/dist/collections/metrics.js +7 -0
- package/dist/collections/metricsGuards.d.ts +9 -0
- package/dist/collections/metricsGuards.js +33 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/contentDone.d.ts +4 -0
- package/dist/contentDone.js +5 -0
- package/dist/db/identities.d.ts +8 -7
- package/dist/db/identities.js +9 -6
- package/dist/db/metricUsage.d.ts +19 -0
- package/dist/db/metricUsage.js +54 -0
- package/dist/db/names.d.ts +5 -0
- package/dist/db/names.js +5 -0
- package/dist/db/publishedContent.d.ts +10 -0
- package/dist/db/publishedContent.js +43 -0
- package/dist/db/results.d.ts +2 -0
- package/dist/db/results.js +6 -2
- package/dist/db/snapshots.d.ts +18 -6
- package/dist/db/snapshots.js +23 -7
- package/dist/db/staleEdit.d.ts +6 -0
- package/dist/db/staleEdit.js +10 -0
- package/dist/db/sweep.js +11 -6
- package/dist/endpoints/events.js +3 -3
- package/dist/endpoints/index.js +19 -1
- package/dist/endpoints/recompute.js +2 -1
- package/dist/endpoints/results.js +5 -0
- package/dist/endpoints/sanitizeReadinessDetail.d.ts +1 -0
- package/dist/endpoints/sanitizeReadinessDetail.js +2 -0
- package/dist/endpoints/summary.js +20 -2
- package/dist/endpoints/transitions.d.ts +15 -0
- package/dist/endpoints/transitions.js +36 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +28 -0
- package/dist/exports/adminClient.d.ts +1 -0
- package/dist/exports/adminClient.js +1 -0
- package/dist/exports/index.d.ts +2 -1
- package/dist/exports/index.js +1 -0
- package/dist/exports/puckEditor.d.ts +1 -0
- package/dist/exports/puckEditor.js +1 -0
- package/dist/exports/rsc.d.ts +5 -1
- package/dist/exports/rsc.js +2 -0
- package/dist/hooks/hostHook.js +3 -2
- package/dist/hooks/pageGuard.d.ts +5 -0
- package/dist/hooks/pageGuard.js +121 -0
- package/dist/labels/arms.d.ts +7 -0
- package/dist/labels/arms.js +7 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +38 -0
- package/dist/manifest/compile.d.ts +5 -0
- package/dist/manifest/compile.js +5 -1
- package/dist/options.js +23 -0
- package/dist/plugin.js +26 -2
- package/dist/puck/copy.d.ts +1 -1
- package/dist/puck/copy.js +6 -6
- package/dist/puck/editor/editor.chrome.css +235 -0
- package/dist/puck/editor/editor.css +241 -43
- package/dist/results/aggregate.js +4 -3
- package/dist/results/analyse.d.ts +4 -1
- package/dist/results/analyse.js +27 -11
- package/dist/results/cuped.js +3 -2
- package/dist/results/identityCredit.d.ts +15 -7
- package/dist/results/identityCredit.js +32 -33
- package/dist/results/rolloutUnits.js +2 -1
- package/dist/results/snapshot.js +8 -2
- package/dist/results/srm.js +2 -1
- package/dist/results/verdict.d.ts +2 -0
- package/dist/results/verdict.js +18 -1
- package/dist/sanitizeReadinessDetail.d.ts +7 -0
- package/dist/sanitizeReadinessDetail.js +54 -0
- package/dist/server/linkIdentity.d.ts +10 -1
- package/dist/server/linkIdentity.js +20 -18
- package/dist/server/metadata.d.ts +5 -0
- package/dist/server/metadata.js +9 -1
- package/dist/server/recordContentChange.js +2 -1
- package/dist/server/recordExposures.js +3 -3
- package/dist/server/runResults.d.ts +3 -2
- package/dist/server/runResults.js +125 -34
- package/dist/server/trackConversion.js +5 -6
- package/dist/targeting/values.d.ts +4 -0
- package/dist/targeting/values.js +10 -0
- package/dist/transitions/addMetric.js +8 -0
- package/dist/transitions/archive.js +2 -3
- package/dist/transitions/checkStart.d.ts +6 -3
- package/dist/transitions/checkStart.js +15 -0
- package/dist/transitions/contentFields.d.ts +7 -0
- package/dist/transitions/contentFields.js +20 -0
- package/dist/transitions/decide.js +6 -1
- package/dist/transitions/deleteExperiment.js +2 -1
- package/dist/transitions/dismissContentEntry.d.ts +8 -0
- package/dist/transitions/dismissContentEntry.js +45 -0
- package/dist/transitions/makeContent.d.ts +5 -2
- package/dist/transitions/makeContent.js +217 -118
- package/dist/transitions/phaseRecord.d.ts +8 -2
- package/dist/transitions/placements.d.ts +1 -0
- package/dist/transitions/placements.js +4 -0
- package/dist/transitions/preflightData.d.ts +1 -1
- package/dist/transitions/preflightData.js +42 -4
- package/dist/transitions/releasePages.d.ts +8 -0
- package/dist/transitions/releasePages.js +26 -0
- package/dist/transitions/restoreContentEntry.d.ts +8 -0
- package/dist/transitions/restoreContentEntry.js +47 -0
- package/dist/transitions/start.js +30 -5
- package/dist/types/contracts.d.ts +14 -0
- package/dist/types/endpoints.d.ts +34 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/reasons.d.ts +3 -0
- package/dist/types/reasons.js +1 -0
- package/dist/types/snapshot.d.ts +6 -1
- package/dist/ui/Banner.d.ts +2 -0
- package/dist/ui/Banner.js +2 -0
- package/dist/ui/Breadcrumb.d.ts +7 -0
- package/dist/ui/Breadcrumb.js +25 -0
- package/dist/ui/Button.d.ts +15 -0
- package/dist/ui/Button.js +32 -0
- package/dist/ui/Card.d.ts +12 -0
- package/dist/ui/Card.js +40 -0
- package/dist/ui/Checkbox.d.ts +2 -0
- package/dist/ui/Checkbox.js +2 -0
- package/dist/{admin/components → ui}/ChipsInput.d.ts +4 -3
- package/dist/{admin/components → ui}/ChipsInput.js +20 -12
- package/dist/ui/Dialog.d.ts +13 -0
- package/dist/ui/Dialog.js +85 -0
- package/dist/ui/EmptyState.d.ts +2 -0
- package/dist/ui/EmptyState.js +2 -0
- package/dist/ui/Feedback.d.ts +24 -0
- package/dist/ui/Feedback.js +40 -0
- package/dist/ui/Inputs.d.ts +34 -0
- package/dist/ui/Inputs.js +225 -0
- package/dist/ui/NumberInput.d.ts +2 -0
- package/dist/ui/NumberInput.js +2 -0
- package/dist/ui/Range.d.ts +2 -0
- package/dist/ui/Range.js +2 -0
- package/dist/ui/SearchInput.d.ts +2 -0
- package/dist/ui/SearchInput.js +2 -0
- package/dist/ui/SectionCard.d.ts +2 -0
- package/dist/ui/SectionCard.js +2 -0
- package/dist/ui/Select.d.ts +2 -0
- package/dist/ui/Select.js +2 -0
- package/dist/ui/StatusChip.d.ts +2 -0
- package/dist/ui/StatusChip.js +2 -0
- package/dist/ui/Tabs.d.ts +14 -0
- package/dist/ui/Tabs.js +54 -0
- package/dist/ui/TextInput.d.ts +2 -0
- package/dist/ui/TextInput.js +2 -0
- package/dist/ui/Textarea.d.ts +2 -0
- package/dist/ui/Textarea.js +2 -0
- package/dist/ui/Toggle.d.ts +2 -0
- package/dist/ui/Toggle.js +2 -0
- package/dist/ui/accent.d.ts +21 -0
- package/dist/ui/accent.js +81 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.js +9 -0
- package/dist/ui/ui.css +92 -0
- package/package.json +1 -1
- package/dist/admin/components/chipsInput.css +0 -11
- package/dist/admin/results/ArmTable.d.ts +0 -8
- package/dist/admin/results/ArmTable.js +0 -113
- package/dist/admin/results/VerdictCard.d.ts +0 -9
- package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +0 -5
- package/dist/admin/surface/goals/GoalsBreadcrumb.js +0 -47
- package/dist/admin/surface/test/TestHeader.d.ts +0 -4
- package/dist/admin/surface/test/TestHeader.js +0 -69
|
@@ -1,49 +1,148 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useState } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
3
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
import { contentDone } from '../../contentDone.js';
|
|
5
|
+
import { Button } from '../../ui/Button.js';
|
|
6
|
+
import { Banner } from '../../ui/Banner.js';
|
|
7
|
+
import { API_MESSAGES } from '../copy/common.js';
|
|
8
|
+
import { ACTIONS_COPY as C, contentReportCopy } from '../copy/actions.js';
|
|
9
|
+
export function MakeContentResult({ result, onRetry, busy = false, id, api, canManage = false, isDecided = false, onFinish, onBusyChange, onActionStart }) {
|
|
7
10
|
const inFlight = useRef(false);
|
|
11
|
+
const container = useRef(null);
|
|
12
|
+
const focusTarget = useRef(null);
|
|
8
13
|
const [pending, setPending] = useState(false);
|
|
9
|
-
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [report, setReport] = useState({
|
|
16
|
+
source: result,
|
|
17
|
+
docs: result.docs
|
|
18
|
+
});
|
|
19
|
+
// A Retry supplies a new report. Entry actions use the authoritative returned doc.
|
|
20
|
+
if (report.source !== result) setReport({
|
|
21
|
+
source: result,
|
|
22
|
+
docs: result.docs
|
|
23
|
+
});
|
|
24
|
+
const docs = report.source === result ? report.docs : result.docs;
|
|
25
|
+
const editable = canManage && isDecided;
|
|
26
|
+
useLayoutEffect(()=>{
|
|
27
|
+
if (busy || pending || !focusTarget.current) return;
|
|
28
|
+
const target = focusTarget.current === 'finish' ? container.current?.querySelector('[data-finish-archiving]') : focusTarget.current;
|
|
29
|
+
target?.focus();
|
|
30
|
+
focusTarget.current = null;
|
|
31
|
+
}, [
|
|
32
|
+
docs,
|
|
33
|
+
busy,
|
|
34
|
+
pending
|
|
35
|
+
]);
|
|
36
|
+
async function run(action) {
|
|
10
37
|
if (inFlight.current || busy) return;
|
|
11
38
|
inFlight.current = true;
|
|
12
39
|
setPending(true);
|
|
40
|
+
setError(null);
|
|
41
|
+
onActionStart?.();
|
|
42
|
+
onBusyChange?.(true);
|
|
13
43
|
try {
|
|
14
|
-
await
|
|
44
|
+
await action();
|
|
45
|
+
} catch {
|
|
46
|
+
setError(API_MESSAGES.network);
|
|
15
47
|
} finally{
|
|
16
48
|
inFlight.current = false;
|
|
17
49
|
setPending(false);
|
|
50
|
+
onBusyChange?.(false);
|
|
18
51
|
}
|
|
19
52
|
}
|
|
20
|
-
|
|
53
|
+
async function changeEntry(entry, button) {
|
|
54
|
+
if (!editable || !api || id === undefined) return;
|
|
55
|
+
const response = await (entry.dismissedAt ? api.restoreContentEntry : api.dismissContentEntry)(id, {
|
|
56
|
+
collection: entry.collection,
|
|
57
|
+
id: entry.id
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
setError(response.message);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const decision = response.data?.doc.decision;
|
|
64
|
+
const updated = decision?.contentReport?.docs?.find((doc)=>doc.collection === entry.collection && String(doc.id) === String(entry.id));
|
|
65
|
+
if (!updated) {
|
|
66
|
+
setError(API_MESSAGES.unreadable);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const updatedDocs = docs.map((doc)=>doc.collection === entry.collection && String(doc.id) === String(entry.id) ? updated : doc);
|
|
70
|
+
focusTarget.current = !entry.dismissedAt && !contentDone(docs) && contentDone(updatedDocs) && onFinish ? 'finish' : button;
|
|
71
|
+
setReport((current)=>({
|
|
72
|
+
...current,
|
|
73
|
+
docs: updatedDocs
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
const retryable = docs.some((doc)=>!doc.ok && !doc.dismissedAt);
|
|
21
77
|
return /*#__PURE__*/ _jsxs("div", {
|
|
78
|
+
ref: container,
|
|
22
79
|
"aria-live": "polite",
|
|
23
80
|
children: [
|
|
81
|
+
error ? /*#__PURE__*/ _jsx(Banner, {
|
|
82
|
+
tone: "error",
|
|
83
|
+
children: error
|
|
84
|
+
}) : null,
|
|
85
|
+
result.doc?.status === 'archived' ? /*#__PURE__*/ _jsx("p", {
|
|
86
|
+
children: C.contentArchived
|
|
87
|
+
}) : null,
|
|
24
88
|
/*#__PURE__*/ _jsx("ul", {
|
|
25
|
-
children:
|
|
89
|
+
children: docs.map((doc, index)=>{
|
|
90
|
+
const label = 'title' in doc && typeof doc.title === 'string' && doc.title || `${doc.collection}/${doc.id}`;
|
|
91
|
+
return /*#__PURE__*/ _jsxs("li", {
|
|
26
92
|
children: [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
93
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
94
|
+
children: [
|
|
95
|
+
label,
|
|
96
|
+
": ",
|
|
97
|
+
doc.ok ? C.updated : doc.dismissedAt ? C.dismissed : C.failed,
|
|
98
|
+
!doc.dismissedAt && (doc.detail !== undefined || doc.error !== undefined || !doc.ok) ? ` — ${contentReportCopy(doc.detail ?? doc.error, doc.ok)}` : ''
|
|
99
|
+
]
|
|
100
|
+
}),
|
|
101
|
+
!doc.ok && editable && api && id !== undefined ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
102
|
+
children: [
|
|
103
|
+
doc.dismissedAt ? ' · ' : ' ',
|
|
104
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
105
|
+
variant: "quiet",
|
|
106
|
+
size: "sm",
|
|
107
|
+
"aria-disabled": busy || pending || undefined,
|
|
108
|
+
"aria-label": doc.dismissedAt ? C.undoLabel(label) : C.dismissLabel(label),
|
|
109
|
+
onClick: (event)=>{
|
|
110
|
+
const button = event.currentTarget;
|
|
111
|
+
void run(()=>changeEntry(doc, button));
|
|
112
|
+
},
|
|
113
|
+
children: doc.dismissedAt ? C.undo : C.dismiss
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
}) : null
|
|
31
117
|
]
|
|
32
|
-
}, `${doc.collection}/${doc.id}/${index}`)
|
|
118
|
+
}, `${doc.collection}/${doc.id}/${index}`);
|
|
119
|
+
})
|
|
33
120
|
}),
|
|
34
|
-
|
|
121
|
+
editable && retryable ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
35
122
|
children: [
|
|
123
|
+
/*#__PURE__*/ _jsx("p", {
|
|
124
|
+
children: C.dismissHelp
|
|
125
|
+
}),
|
|
36
126
|
/*#__PURE__*/ _jsx("p", {
|
|
37
127
|
children: C.partial
|
|
38
128
|
}),
|
|
39
129
|
/*#__PURE__*/ _jsx(Button, {
|
|
40
|
-
disabled: busy || pending,
|
|
130
|
+
"aria-disabled": busy || pending || undefined,
|
|
41
131
|
onClick: ()=>{
|
|
42
|
-
void
|
|
132
|
+
void run(onRetry);
|
|
43
133
|
},
|
|
44
134
|
children: C.retry
|
|
45
135
|
})
|
|
46
136
|
]
|
|
137
|
+
}) : null,
|
|
138
|
+
editable && contentDone(docs) && onFinish ? /*#__PURE__*/ _jsx(Button, {
|
|
139
|
+
"data-finish-archiving": true,
|
|
140
|
+
variant: "primary",
|
|
141
|
+
disabled: busy || pending,
|
|
142
|
+
onClick: ()=>{
|
|
143
|
+
void run(onFinish);
|
|
144
|
+
},
|
|
145
|
+
children: C.finish
|
|
47
146
|
}) : null
|
|
48
147
|
]
|
|
49
148
|
});
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { ExperimentKind, Status } from '../../types/contracts.js';
|
|
2
|
-
import type { ArchiveBody,
|
|
2
|
+
import type { ArchiveBody, ContentReport } from '../../types/endpoints.js';
|
|
3
3
|
export type ActionGroup = {
|
|
4
4
|
key: string;
|
|
5
5
|
label?: string;
|
|
6
6
|
kind?: string;
|
|
7
7
|
};
|
|
8
|
-
export type ContentReport
|
|
9
|
-
docs: MakeContentResult['docs'];
|
|
10
|
-
} | MakeContentResult['docs'];
|
|
8
|
+
export type { ContentReport } from '../../types/endpoints.js';
|
|
11
9
|
export type ActionDecision = {
|
|
12
10
|
winnerArm?: string | null;
|
|
13
11
|
holdoutReleasedAt?: string | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { contentDone } from '../../contentDone.js';
|
|
1
2
|
import { armLabel } from '../labels.js';
|
|
2
3
|
import { ACTIONS_COPY as C, hardCodedLabel, makeContentCopy, releaseCopy } from '../copy/actions.js';
|
|
3
4
|
import { REVIEW_COPY } from '../copy/review.js';
|
|
@@ -45,8 +46,6 @@ export function availableActions({ status, kind, arms, decision, canManage }) {
|
|
|
45
46
|
const winner = arms.find((group)=>group.key === decision?.winnerArm);
|
|
46
47
|
const label = actionGroupLabel(decision?.winnerArm ?? 'control', winner?.label);
|
|
47
48
|
const rolloutOpen = arms.some((group)=>group.key === 'holdout') && Boolean(decision?.winnerArm) && decision?.winnerArm !== 'control' && !decision?.holdoutReleasedAt;
|
|
48
|
-
const report = decision?.contentReport;
|
|
49
|
-
const docs = Array.isArray(report) ? report : report?.docs;
|
|
50
49
|
return [
|
|
51
50
|
...rolloutOpen ? [
|
|
52
51
|
action('release-holdout', C.release, releaseCopy(label))
|
|
@@ -54,7 +53,7 @@ export function availableActions({ status, kind, arms, decision, canManage }) {
|
|
|
54
53
|
...kind !== 'code' ? [
|
|
55
54
|
action('make-content', C.makeContent, makeContentCopy(label, kind), 'primary')
|
|
56
55
|
] : [],
|
|
57
|
-
...
|
|
56
|
+
...contentDone(decision?.contentReport) ? [
|
|
58
57
|
action('after-content', C.finish, C.finishBody, 'secondary', {
|
|
59
58
|
mode: 'after-content'
|
|
60
59
|
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { armLabel } from './labels.js';
|
|
2
|
-
import {
|
|
3
|
-
import { eachVersion, groupShare, holdoutClause, layerSentence, participation, peopleLabel, uncounted } from './copy/audience.js';
|
|
2
|
+
import { eachVersion, groupShare, holdoutClause, holdoutRemainder, layerSentence, participation, peopleLabel, uncounted } from './copy/audience.js';
|
|
4
3
|
const percent = (value)=>String(Math.round(value * 10) / 10);
|
|
5
4
|
export function allocationSentence({ allocation, unitType, arms, layer, status }) {
|
|
6
5
|
const groups = arms.filter((group)=>group.kind !== 'holdout');
|
|
@@ -8,10 +7,9 @@ export function allocationSentence({ allocation, unitType, arms, layer, status }
|
|
|
8
7
|
const equal = groups.every((group)=>group.weight === groups[0].weight);
|
|
9
8
|
const shares = equal ? eachVersion(percent(allocation * groups[0].weight / 100)) : groups.map((group)=>groupShare(percent(allocation * group.weight / 100), armLabel(group.key, group.label))).join(', ');
|
|
10
9
|
const holdout = arms.find((group)=>group.kind === 'holdout');
|
|
11
|
-
let sentence = participation(percent(allocation), peopleLabel[unitType], shares) + (holdout ? holdoutClause(percent(holdout.weight)) : uncounted);
|
|
12
|
-
if ((layer || status === 'running') && !sentence.endsWith('.')) sentence += '.';
|
|
10
|
+
let sentence = participation(percent(allocation), peopleLabel[unitType], shares) + (holdout ? holdoutClause(percent(holdout.weight)) : '') + (allocation < 100 ? holdout ? holdoutRemainder : uncounted : '');
|
|
11
|
+
if ((holdout || layer || status === 'running') && !sentence.endsWith('.')) sentence += '.';
|
|
13
12
|
if (layer) sentence += layerSentence(layer.name, layer.used, layer.free);
|
|
14
|
-
if (status === 'running') sentence += ` ${LOCKED_MESSAGES.runningAllocation}`;
|
|
15
13
|
return sentence;
|
|
16
14
|
}
|
|
17
15
|
// Payload arrays are flattened into row fields; their parent value is a row count.
|
package/dist/admin/api.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DocId } from '../types/contracts.js';
|
|
2
|
-
import type { AddMetricBody, AllocateBody, ArchiveBody, DecideBody, MakeContentResult, PreflightResult, PreviewTokenResult, RefusalCode, ResultsResponse, SummaryResponse, TransitionSuccess } from '../types/endpoints.js';
|
|
2
|
+
import type { RestoreContentEntryBody, RestoreContentEntryResult, DismissContentEntryBody, DismissContentEntryResult, AddMetricBody, AllocateBody, ArchiveBody, DecideBody, MakeContentResult, PreflightResult, PreviewTokenResult, RefusalCode, ResultsResponse, SummaryResponse, TransitionSuccess } from '../types/endpoints.js';
|
|
3
|
+
import { refusalMessage } from './copy/common.js';
|
|
3
4
|
export type ApiResult<T> = {
|
|
4
5
|
ok: true;
|
|
5
6
|
data: T | undefined;
|
|
@@ -10,23 +11,28 @@ export type ApiResult<T> = {
|
|
|
10
11
|
message: string;
|
|
11
12
|
retryable: boolean;
|
|
12
13
|
};
|
|
14
|
+
type RequestOptions = NonNullable<Parameters<typeof refusalMessage>[3]>;
|
|
13
15
|
export declare function createApi(apiBase: string): {
|
|
14
16
|
start: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
|
|
15
|
-
allocate: (id: DocId, body: AllocateBody) => Promise<ApiResult<TransitionSuccess>>;
|
|
16
|
-
addMetric: (id: DocId, body: AddMetricBody) => Promise<ApiResult<TransitionSuccess>>;
|
|
17
|
+
allocate: (id: DocId, body: AllocateBody, options?: RequestOptions) => Promise<ApiResult<TransitionSuccess>>;
|
|
18
|
+
addMetric: (id: DocId, body: AddMetricBody, options?: RequestOptions) => Promise<ApiResult<TransitionSuccess>>;
|
|
17
19
|
stop: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
|
|
18
20
|
decide: (id: DocId, body: DecideBody) => Promise<ApiResult<TransitionSuccess>>;
|
|
19
21
|
releaseHoldout: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
|
|
22
|
+
releasePages: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
|
|
20
23
|
makeContent: (id: DocId) => Promise<ApiResult<MakeContentResult>>;
|
|
24
|
+
dismissContentEntry: (id: DocId, body: DismissContentEntryBody) => Promise<ApiResult<DismissContentEntryResult>>;
|
|
25
|
+
restoreContentEntry: (id: DocId, body: RestoreContentEntryBody) => Promise<ApiResult<RestoreContentEntryResult>>;
|
|
21
26
|
archive: (id: DocId, body: ArchiveBody) => Promise<ApiResult<TransitionSuccess>>;
|
|
22
27
|
preflight: (id: DocId) => Promise<ApiResult<PreflightResult>>;
|
|
23
28
|
previewToken: (id: DocId, arm: string) => Promise<ApiResult<PreviewTokenResult>>;
|
|
24
29
|
results: (id: DocId, options?: {
|
|
25
30
|
phase?: number;
|
|
26
31
|
series?: boolean;
|
|
27
|
-
}) => Promise<ApiResult<ResultsResponse>>;
|
|
32
|
+
} & RequestOptions) => Promise<ApiResult<ResultsResponse>>;
|
|
28
33
|
recompute: (id: DocId) => Promise<ApiResult<{
|
|
29
34
|
snapshotId: string;
|
|
30
35
|
}>>;
|
|
31
36
|
summary: (ids: readonly DocId[]) => Promise<ApiResult<SummaryResponse>>;
|
|
32
37
|
};
|
|
38
|
+
export {};
|
package/dist/admin/api.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { API_MESSAGES, refusalMessage } from './copy/common.js';
|
|
2
|
+
import { CONTENT_ENTRY_REFUSALS } from './copy/actions.js';
|
|
2
3
|
export function createApi(apiBase) {
|
|
3
4
|
const base = `${apiBase.replace(/\/$/, '')}/xp/experiments`;
|
|
4
5
|
const route = (id, action)=>`${base}/${encodeURIComponent(id)}/${action}`;
|
|
5
|
-
async function request(path, method, body) {
|
|
6
|
+
async function request(path, method, body, options) {
|
|
6
7
|
try {
|
|
7
8
|
const response = await fetch(path(), {
|
|
8
9
|
method,
|
|
@@ -28,7 +29,7 @@ export function createApi(apiBase) {
|
|
|
28
29
|
...response.status === 501 ? {
|
|
29
30
|
code: 'not-implemented'
|
|
30
31
|
} : {},
|
|
31
|
-
message: refusalMessage(undefined, response.status),
|
|
32
|
+
message: refusalMessage(undefined, response.status, undefined, options),
|
|
32
33
|
retryable: response.status >= 500 && response.status !== 501
|
|
33
34
|
};
|
|
34
35
|
}
|
|
@@ -46,7 +47,7 @@ export function createApi(apiBase) {
|
|
|
46
47
|
...code ? {
|
|
47
48
|
code
|
|
48
49
|
} : {},
|
|
49
|
-
message: refusalMessage(code, response.status, typeof error?.message === 'string' ? error.message : undefined),
|
|
50
|
+
message: refusalMessage(code, response.status, typeof error?.message === 'string' ? error.message : undefined, options),
|
|
50
51
|
retryable: code === 'commit-unverified' || response.status >= 500 && response.status !== 501
|
|
51
52
|
};
|
|
52
53
|
} catch {
|
|
@@ -58,15 +59,18 @@ export function createApi(apiBase) {
|
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
|
-
const post = (id, action, body)=>request(()=>route(id, action), 'POST', body);
|
|
62
|
+
const post = (id, action, body, options)=>request(()=>route(id, action), 'POST', body, options);
|
|
62
63
|
return {
|
|
63
64
|
start: (id)=>post(id, 'start'),
|
|
64
|
-
allocate: (id, body)=>post(id, 'allocate', body),
|
|
65
|
-
addMetric: (id, body)=>post(id, 'add-metric', body),
|
|
65
|
+
allocate: (id, body, options)=>post(id, 'allocate', body, options),
|
|
66
|
+
addMetric: (id, body, options)=>post(id, 'add-metric', body, options),
|
|
66
67
|
stop: (id)=>post(id, 'stop'),
|
|
67
68
|
decide: (id, body)=>post(id, 'decide', body),
|
|
68
69
|
releaseHoldout: (id)=>post(id, 'release-holdout'),
|
|
70
|
+
releasePages: (id)=>post(id, 'release-pages'),
|
|
69
71
|
makeContent: (id)=>post(id, 'make-content'),
|
|
72
|
+
dismissContentEntry: (id, body)=>post(id, 'content-report/dismiss', body, CONTENT_ENTRY_REFUSALS),
|
|
73
|
+
restoreContentEntry: (id, body)=>post(id, 'content-report/restore', body, CONTENT_ENTRY_REFUSALS),
|
|
70
74
|
archive: (id, body)=>post(id, 'archive', body),
|
|
71
75
|
preflight: (id)=>request(()=>route(id, 'preflight'), 'GET'),
|
|
72
76
|
previewToken: (id, arm)=>request(()=>`${route(id, 'preview-token')}?arm=${encodeURIComponent(arm)}`, 'GET'),
|
|
@@ -76,7 +80,7 @@ export function createApi(apiBase) {
|
|
|
76
80
|
if (options.series) query.set('series', '1');
|
|
77
81
|
const qs = query.toString();
|
|
78
82
|
return `${route(id, 'results')}${qs ? `?${qs}` : ''}`;
|
|
79
|
-
}, 'GET'),
|
|
83
|
+
}, 'GET', undefined, options),
|
|
80
84
|
recompute: (id)=>post(id, 'recompute'),
|
|
81
85
|
summary: async (ids)=>{
|
|
82
86
|
const data = {};
|
|
@@ -8,13 +8,13 @@ export declare const ACTIONS_COPY: {
|
|
|
8
8
|
readonly finish: "Finish archiving";
|
|
9
9
|
readonly archive: "Archive";
|
|
10
10
|
readonly discard: "Discard winner";
|
|
11
|
-
readonly startBody: "Start this
|
|
11
|
+
readonly startBody: "Start this test?";
|
|
12
12
|
readonly restartBody: "Starting again begins a new round; results restart.";
|
|
13
13
|
readonly stopBody: "Everyone will see the Original. Starting again begins a new round; results restart.";
|
|
14
14
|
readonly original: "Everyone will see the Original.";
|
|
15
15
|
readonly discardBody: "Everyone goes back to the Original. The decision note is kept.";
|
|
16
|
-
readonly archiveBody: "Archive this
|
|
17
|
-
readonly finishBody: "The content has been updated. Finish archiving this
|
|
16
|
+
readonly archiveBody: "Archive this test?";
|
|
17
|
+
readonly finishBody: "The content has been updated. Finish archiving this test.";
|
|
18
18
|
readonly noResults: "There are no results yet.";
|
|
19
19
|
readonly noRecommendation: "The results don't recommend a group yet.";
|
|
20
20
|
readonly different: "You chose a different group than the results recommend.";
|
|
@@ -25,12 +25,48 @@ export declare const ACTIONS_COPY: {
|
|
|
25
25
|
readonly cancel: "Cancel";
|
|
26
26
|
readonly retry: "Retry";
|
|
27
27
|
readonly done: "Done";
|
|
28
|
+
readonly dismiss: "Dismiss";
|
|
29
|
+
readonly dismissed: "Dismissed";
|
|
30
|
+
readonly undo: "Undo";
|
|
31
|
+
readonly contentArchived: "The winner is now the content and the test is archived.";
|
|
32
|
+
readonly dismissLabel: (page: string) => string;
|
|
33
|
+
readonly undoLabel: (page: string) => string;
|
|
34
|
+
readonly dismissHelp: "Dismiss a page you'll update by hand, then finish archiving.";
|
|
35
|
+
readonly contentEntryNotDecided: "This test isn't decided, so its pages can't be changed.";
|
|
36
|
+
readonly contentEntryMissing: "This page is no longer in the list. Reload to see the current list.";
|
|
37
|
+
readonly contentEntryDismissed: "This page was already dismissed.";
|
|
38
|
+
readonly contentEntryNotDismissed: "This page isn't dismissed. Reload to see its current state.";
|
|
39
|
+
readonly contentEntryUpdated: "This page is already updated.";
|
|
40
|
+
readonly contentEntryNotFound: "This test no longer exists.";
|
|
41
|
+
readonly contentEntryFallback: "This page can't be changed right now. Reload to see its current state.";
|
|
28
42
|
readonly loading: "Loading results…";
|
|
29
43
|
readonly updated: "Updated";
|
|
30
44
|
readonly failed: "Failed";
|
|
45
|
+
readonly contentReasons: {
|
|
46
|
+
localized: string;
|
|
47
|
+
locked: string;
|
|
48
|
+
forbidden: string;
|
|
49
|
+
'not-found': string;
|
|
50
|
+
validation: string;
|
|
51
|
+
'update-failed': string;
|
|
52
|
+
};
|
|
53
|
+
readonly missingContent: "This page no longer exists; nothing to change.";
|
|
31
54
|
readonly partial: "Some pages couldn't be updated. Fix them and retry.";
|
|
32
55
|
};
|
|
33
56
|
export declare const decideCopy: (winner: string, holdout: boolean, original: boolean) => string;
|
|
34
57
|
export declare const releaseCopy: (winner: string) => string;
|
|
35
58
|
export declare const makeContentCopy: (winner: string, kind: "block" | "page" | "flag") => string;
|
|
36
59
|
export declare const hardCodedLabel: (winner: string) => string;
|
|
60
|
+
/** Old reports can contain host error text: never interpolate an unknown string. */
|
|
61
|
+
export declare const contentReportCopy: (reason: string | undefined, ok?: boolean) => string;
|
|
62
|
+
export declare const CONTENT_ENTRY_REFUSALS: {
|
|
63
|
+
readonly notFound: "This test no longer exists.";
|
|
64
|
+
readonly codes: {
|
|
65
|
+
readonly 'not-decided': "This test isn't decided, so its pages can't be changed.";
|
|
66
|
+
readonly 'entry-not-found': "This page is no longer in the list. Reload to see the current list.";
|
|
67
|
+
readonly 'entry-ok': "This page is already updated.";
|
|
68
|
+
readonly 'entry-dismissed': "This page was already dismissed.";
|
|
69
|
+
readonly 'entry-not-dismissed': "This page isn't dismissed. Reload to see its current state.";
|
|
70
|
+
};
|
|
71
|
+
readonly fallback: "This page can't be changed right now. Reload to see its current state.";
|
|
72
|
+
};
|
|
@@ -8,13 +8,13 @@ export const ACTIONS_COPY = {
|
|
|
8
8
|
finish: 'Finish archiving',
|
|
9
9
|
archive: 'Archive',
|
|
10
10
|
discard: 'Discard winner',
|
|
11
|
-
startBody: 'Start this
|
|
11
|
+
startBody: 'Start this test?',
|
|
12
12
|
restartBody: 'Starting again begins a new round; results restart.',
|
|
13
13
|
stopBody: 'Everyone will see the Original. Starting again begins a new round; results restart.',
|
|
14
14
|
original: 'Everyone will see the Original.',
|
|
15
15
|
discardBody: 'Everyone goes back to the Original. The decision note is kept.',
|
|
16
|
-
archiveBody: 'Archive this
|
|
17
|
-
finishBody: 'The content has been updated. Finish archiving this
|
|
16
|
+
archiveBody: 'Archive this test?',
|
|
17
|
+
finishBody: 'The content has been updated. Finish archiving this test.',
|
|
18
18
|
noResults: 'There are no results yet.',
|
|
19
19
|
noRecommendation: "The results don't recommend a group yet.",
|
|
20
20
|
different: 'You chose a different group than the results recommend.',
|
|
@@ -25,12 +25,48 @@ export const ACTIONS_COPY = {
|
|
|
25
25
|
cancel: 'Cancel',
|
|
26
26
|
retry: 'Retry',
|
|
27
27
|
done: 'Done',
|
|
28
|
+
dismiss: 'Dismiss',
|
|
29
|
+
dismissed: 'Dismissed',
|
|
30
|
+
undo: 'Undo',
|
|
31
|
+
contentArchived: 'The winner is now the content and the test is archived.',
|
|
32
|
+
dismissLabel: (page)=>`Dismiss ${page}`,
|
|
33
|
+
undoLabel: (page)=>`Undo dismiss ${page}`,
|
|
34
|
+
dismissHelp: "Dismiss a page you'll update by hand, then finish archiving.",
|
|
35
|
+
contentEntryNotDecided: "This test isn't decided, so its pages can't be changed.",
|
|
36
|
+
contentEntryMissing: 'This page is no longer in the list. Reload to see the current list.',
|
|
37
|
+
contentEntryDismissed: 'This page was already dismissed.',
|
|
38
|
+
contentEntryNotDismissed: "This page isn't dismissed. Reload to see its current state.",
|
|
39
|
+
contentEntryUpdated: 'This page is already updated.',
|
|
40
|
+
contentEntryNotFound: 'This test no longer exists.',
|
|
41
|
+
contentEntryFallback: "This page can't be changed right now. Reload to see its current state.",
|
|
28
42
|
loading: 'Loading results…',
|
|
29
43
|
updated: 'Updated',
|
|
30
44
|
failed: 'Failed',
|
|
45
|
+
contentReasons: {
|
|
46
|
+
localized: "This page has more than one language; Make it the content can't publish it safely yet. Copy the winning version by hand.",
|
|
47
|
+
locked: 'Someone is editing this page. Wait for them to finish, then retry.',
|
|
48
|
+
forbidden: "You can't read or update this page. Ask someone with access to update it by hand. If it was deleted or moved to trash, discard the winner to archive this test.",
|
|
49
|
+
'not-found': 'This page could not be found. Check whether it was deleted.',
|
|
50
|
+
validation: "This page couldn't be saved automatically. Update it by hand, then retry.",
|
|
51
|
+
'update-failed': "This page couldn't be updated this time. Retry; if it keeps happening, ask a developer."
|
|
52
|
+
},
|
|
53
|
+
missingContent: 'This page no longer exists; nothing to change.',
|
|
31
54
|
partial: "Some pages couldn't be updated. Fix them and retry."
|
|
32
55
|
};
|
|
33
56
|
export const decideCopy = (winner, holdout, original)=>original ? ACTIONS_COPY.original : holdout ? `Everyone except the holdout will see ${winner}. The holdout keeps seeing the Original so we can measure the shipped change.` : `Everyone will see ${winner}.`;
|
|
34
57
|
export const releaseCopy = (winner)=>`The holdout will see ${winner} too. Measuring since shipping ends.`;
|
|
35
|
-
export const makeContentCopy = (winner, kind)=>kind === 'flag' ? `Saves ${winner}'s settings as the new defaults so the
|
|
58
|
+
export const makeContentCopy = (winner, kind)=>kind === 'flag' ? `Saves ${winner}'s settings as the new defaults so the test can be archived.` : `Copies ${winner} into the page content so the test can be archived.`;
|
|
36
59
|
export const hardCodedLabel = (winner)=>`The developer has hard-coded ${winner}`;
|
|
60
|
+
/** Old reports can contain host error text: never interpolate an unknown string. */ export const contentReportCopy = (reason, ok = false)=>reason === 'not-found' && ok ? ACTIONS_COPY.missingContent : ACTIONS_COPY.contentReasons[reason !== undefined && Object.hasOwn(ACTIONS_COPY.contentReasons, reason) ? reason : 'update-failed'];
|
|
61
|
+
// Each dismiss/restore refusal code has its own sentence; server text is never shown.
|
|
62
|
+
export const CONTENT_ENTRY_REFUSALS = {
|
|
63
|
+
notFound: ACTIONS_COPY.contentEntryNotFound,
|
|
64
|
+
codes: {
|
|
65
|
+
'not-decided': ACTIONS_COPY.contentEntryNotDecided,
|
|
66
|
+
'entry-not-found': ACTIONS_COPY.contentEntryMissing,
|
|
67
|
+
'entry-ok': ACTIONS_COPY.contentEntryUpdated,
|
|
68
|
+
'entry-dismissed': ACTIONS_COPY.contentEntryDismissed,
|
|
69
|
+
'entry-not-dismissed': ACTIONS_COPY.contentEntryNotDismissed
|
|
70
|
+
},
|
|
71
|
+
fallback: ACTIONS_COPY.contentEntryFallback
|
|
72
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export const ARM_SELECT_COPY = {
|
|
2
|
-
experiment: '
|
|
2
|
+
experiment: 'Test',
|
|
3
3
|
group: 'Group',
|
|
4
4
|
clear: 'Clear',
|
|
5
|
-
forbidden: "You can't see
|
|
6
|
-
unavailable: "
|
|
7
|
-
loading: 'Loading
|
|
5
|
+
forbidden: "You can't see tests. Ask an admin.",
|
|
6
|
+
unavailable: "Tests couldn't be loaded. Reload and try again.",
|
|
7
|
+
loading: 'Loading tests…',
|
|
8
8
|
running: 'Changing this content mid-test mixes two versions in one result',
|
|
9
9
|
noFlagPoint: 'No setting is configured for this field.'
|
|
10
10
|
};
|
|
11
|
-
export const staleExperiment = (name)=>`This was set for an earlier
|
|
11
|
+
export const staleExperiment = (name)=>`This was set for an earlier test${name ? ` (${name})` : ''} and won't be used now.`;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import type { ArmIssueCode } from '../../types/endpoints.js';
|
|
2
|
+
export { ARM_LABELS as GROUP_LABELS } from '../../labels/arms.js';
|
|
2
3
|
export declare const ISSUE_COPY: Record<ArmIssueCode, string>;
|
|
3
|
-
export declare const GROUP_LABELS: {
|
|
4
|
-
readonly control: "Original";
|
|
5
|
-
readonly variantA: "Variant A";
|
|
6
|
-
readonly variantB: "Variant B";
|
|
7
|
-
readonly variantC: "Variant C";
|
|
8
|
-
readonly holdout: "Holdout";
|
|
9
|
-
};
|
|
10
4
|
export declare const ARMS_COPY: {
|
|
11
5
|
readonly holdout: "The holdout sees the Original and is measured but not compared during the test. After you decide, it keeps seeing the Original so the winner can be compared with it.";
|
|
12
6
|
readonly pageRequired: "Enter a page path for this group.";
|
package/dist/admin/copy/arms.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { ARM_LABELS as GROUP_LABELS } from '../../labels/arms.js';
|
|
1
2
|
export const ISSUE_COPY = {
|
|
2
3
|
'weights-total': 'Weights must add up to 100%. They add up to {total}% now.',
|
|
3
4
|
'control-count': 'Include exactly one Original.',
|
|
@@ -10,13 +11,6 @@ export const ISSUE_COPY = {
|
|
|
10
11
|
'slot-required': 'Choose the matching content slot for this group.',
|
|
11
12
|
'invalid-key': 'Use the standard group names: Original, Variant A–C, Holdout.'
|
|
12
13
|
};
|
|
13
|
-
export const GROUP_LABELS = {
|
|
14
|
-
control: 'Original',
|
|
15
|
-
variantA: 'Variant A',
|
|
16
|
-
variantB: 'Variant B',
|
|
17
|
-
variantC: 'Variant C',
|
|
18
|
-
holdout: 'Holdout'
|
|
19
|
-
};
|
|
20
14
|
export const ARMS_COPY = {
|
|
21
15
|
holdout: 'The holdout sees the Original and is measured but not compared during the test. After you decide, it keeps seeing the Original so the winner can be compared with it.',
|
|
22
16
|
pageRequired: 'Enter a page path for this group.',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const AUDIENCE_COPY: {
|
|
2
2
|
readonly listPlaceholder: "Type a value and press Enter";
|
|
3
|
-
readonly listDescription: "Add one value per chip
|
|
3
|
+
readonly listDescription: "Add one value per chip.";
|
|
4
4
|
readonly removeValue: (value: string) => string;
|
|
5
5
|
readonly removeSymbol: "×";
|
|
6
6
|
readonly emptyListNote: "Add at least one value — an empty list can't be saved.";
|
|
@@ -25,6 +25,7 @@ export declare const eachVersion: (share: string) => string;
|
|
|
25
25
|
export declare const groupShare: (share: string, label: string) => string;
|
|
26
26
|
export declare const uncounted = "; the rest see the Original and are not counted";
|
|
27
27
|
export declare const holdoutClause: (weight: string) => string;
|
|
28
|
+
export declare const holdoutRemainder = "; everyone else sees the Original and is not counted";
|
|
28
29
|
export declare const layerSentence: (name: string, used: number, free: number) => string;
|
|
29
30
|
export declare const peopleLabel: {
|
|
30
31
|
readonly visitor: "visitors";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const AUDIENCE_COPY = {
|
|
2
2
|
listPlaceholder: 'Type a value and press Enter',
|
|
3
|
-
listDescription: 'Add one value per chip
|
|
3
|
+
listDescription: 'Add one value per chip.',
|
|
4
4
|
removeValue: (value)=>`Remove ${value}`,
|
|
5
5
|
removeSymbol: '×',
|
|
6
6
|
emptyListNote: "Add at least one value — an empty list can't be saved."
|
|
@@ -24,7 +24,8 @@ export const participation = (allocation, people, groups)=>`${allocation}% of ${
|
|
|
24
24
|
export const eachVersion = (share)=>`${share}% see each version`;
|
|
25
25
|
export const groupShare = (share, label)=>`${share}% see ${label === 'Original' ? 'the Original' : label}`;
|
|
26
26
|
export const uncounted = '; the rest see the Original and are not counted';
|
|
27
|
-
export const holdoutClause = (weight)=>`; the holdout (${weight}% of them) sees the Original and is measured
|
|
27
|
+
export const holdoutClause = (weight)=>`; the holdout (${weight}% of them) sees the Original and is measured`;
|
|
28
|
+
export const holdoutRemainder = '; everyone else sees the Original and is not counted';
|
|
28
29
|
export const layerSentence = (name, used, free)=>` Uses ${used} of the 100 slices in layer ${name}; ${free} are free.`;
|
|
29
30
|
export const peopleLabel = {
|
|
30
31
|
visitor: 'visitors',
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export declare const API_MESSAGES: {
|
|
2
|
-
readonly notFound: "This
|
|
2
|
+
readonly notFound: "This test no longer exists.";
|
|
3
|
+
readonly goalNotFound: "That goal no longer exists. Choose another goal.";
|
|
4
|
+
readonly roundChanged: "This round has changed. Reload the page.";
|
|
5
|
+
readonly roundNotFound: "That round no longer exists. Choose another round.";
|
|
3
6
|
readonly commitUnverified: "The change may not have been saved. Reload and try again.";
|
|
4
7
|
readonly notImplemented: "This isn't available yet.";
|
|
5
8
|
readonly unauthenticated: "Your session has expired. Sign in again.";
|
|
@@ -9,12 +12,16 @@ export declare const API_MESSAGES: {
|
|
|
9
12
|
readonly unreadable: "Couldn't read the server's response. Reload and try again.";
|
|
10
13
|
};
|
|
11
14
|
export declare const LOCKED_MESSAGES: {
|
|
12
|
-
readonly creation: "Set when the
|
|
15
|
+
readonly creation: "Set when the test was created.";
|
|
13
16
|
readonly running: "Locked while the test is running so everyone in this round sees the same thing. Stop the test to change it.";
|
|
14
|
-
readonly runningAllocation: "
|
|
15
|
-
readonly runningMetrics: "Add
|
|
17
|
+
readonly runningAllocation: "To include more people, use \"Let more people in\" at the bottom of the page. To include fewer, stop the test; the change applies when you start it again.";
|
|
18
|
+
readonly runningMetrics: "Add a guardrail or something to watch with \"Add a goal\" at the bottom of the page; the main goal is fixed for this round.";
|
|
16
19
|
readonly decided: "Locked after the decision.";
|
|
17
|
-
readonly archived: "Archived
|
|
20
|
+
readonly archived: "Archived tests can't be changed.";
|
|
18
21
|
readonly other: "Can't be changed in this state.";
|
|
19
22
|
};
|
|
20
|
-
export declare function refusalMessage(code: string | undefined, status: number, message?: string
|
|
23
|
+
export declare function refusalMessage(code: string | undefined, status: number, message?: string, options?: {
|
|
24
|
+
notFound?: string;
|
|
25
|
+
codes?: Readonly<Record<string, string>>;
|
|
26
|
+
fallback?: string;
|
|
27
|
+
}): string;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export const API_MESSAGES = {
|
|
2
|
-
notFound: 'This
|
|
2
|
+
notFound: 'This test no longer exists.',
|
|
3
|
+
goalNotFound: 'That goal no longer exists. Choose another goal.',
|
|
4
|
+
roundChanged: 'This round has changed. Reload the page.',
|
|
5
|
+
roundNotFound: 'That round no longer exists. Choose another round.',
|
|
3
6
|
commitUnverified: 'The change may not have been saved. Reload and try again.',
|
|
4
7
|
notImplemented: "This isn't available yet.",
|
|
5
8
|
unauthenticated: 'Your session has expired. Sign in again.',
|
|
@@ -9,19 +12,22 @@ export const API_MESSAGES = {
|
|
|
9
12
|
unreadable: "Couldn't read the server's response. Reload and try again."
|
|
10
13
|
};
|
|
11
14
|
export const LOCKED_MESSAGES = {
|
|
12
|
-
creation: 'Set when the
|
|
15
|
+
creation: 'Set when the test was created.',
|
|
13
16
|
running: 'Locked while the test is running so everyone in this round sees the same thing. Stop the test to change it.',
|
|
14
|
-
runningAllocation: '
|
|
15
|
-
runningMetrics: 'Add
|
|
17
|
+
runningAllocation: 'To include more people, use "Let more people in" at the bottom of the page. To include fewer, stop the test; the change applies when you start it again.',
|
|
18
|
+
runningMetrics: 'Add a guardrail or something to watch with "Add a goal" at the bottom of the page; the main goal is fixed for this round.',
|
|
16
19
|
decided: 'Locked after the decision.',
|
|
17
|
-
archived: "Archived
|
|
20
|
+
archived: "Archived tests can't be changed.",
|
|
18
21
|
other: "Can't be changed in this state."
|
|
19
22
|
};
|
|
20
|
-
export function refusalMessage(code, status, message) {
|
|
23
|
+
export function refusalMessage(code, status, message, options) {
|
|
24
|
+
// A per-call code wins over the status branches: entry-not-found is a 404 that is not "this test no longer exists".
|
|
25
|
+
const mapped = code && options?.codes && Object.hasOwn(options.codes, code) ? options.codes[code] : undefined;
|
|
26
|
+
if (mapped !== undefined) return mapped;
|
|
21
27
|
if (code === 'unauthenticated' || status === 401) return API_MESSAGES.unauthenticated;
|
|
22
|
-
if (code === 'not-found' || status === 404) return API_MESSAGES.notFound;
|
|
28
|
+
if (code === 'not-found' || status === 404) return options?.notFound ?? API_MESSAGES.notFound;
|
|
23
29
|
if (code === 'commit-unverified') return API_MESSAGES.commitUnverified;
|
|
24
30
|
if (code === 'not-implemented' || status === 501) return API_MESSAGES.notImplemented;
|
|
25
31
|
if (code === 'forbidden' || status === 403) return API_MESSAGES.forbidden;
|
|
26
|
-
return message || API_MESSAGES.failed;
|
|
32
|
+
return options?.fallback ?? (message || API_MESSAGES.failed);
|
|
27
33
|
}
|