@delmaredigital/payload-experiments 0.2.0-rc.7 → 0.3.0-rc.4
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 +202 -26
- package/dist/admin/PreviewLinks.d.ts +2 -1
- package/dist/admin/PreviewLinks.js +50 -21
- package/dist/admin/TestsNavLink.d.ts +3 -1
- package/dist/admin/TestsNavLink.js +2 -2
- 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/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/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 -6
- package/dist/admin/copy/arms.js +1 -16
- 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 +22 -0
- package/dist/admin/copy/readiness.js +21 -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 +43 -2
- package/dist/admin/copy/results.js +44 -3
- package/dist/admin/copy/review.d.ts +2 -2
- package/dist/admin/copy/review.js +2 -2
- 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 -5
- package/dist/admin/copy/surface.js +9 -5
- package/dist/admin/copy/testPage.d.ts +10 -0
- package/dist/admin/copy/testPage.js +10 -0
- package/dist/admin/format/date.d.ts +2 -0
- package/dist/admin/format/date.js +10 -0
- package/dist/admin/paths.d.ts +2 -1
- package/dist/admin/paths.js +2 -1
- package/dist/admin/previewLinks.model.d.ts +4 -2
- package/dist/admin/previewLinks.model.js +8 -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/legacy.d.ts +16 -0
- package/dist/admin/results/legacy.js +27 -0
- 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 +11 -7
- package/dist/admin/results/results.model.js +24 -7
- package/dist/admin/results/snapshot.d.ts +14 -0
- package/dist/admin/results/snapshot.js +3 -0
- package/dist/admin/surface/ResultsRoute.d.ts +1 -1
- package/dist/admin/surface/ResultsRoute.js +59 -34
- 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 +182 -114
- package/dist/admin/surface/TestsSurface.js +56 -16
- 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 +24 -16
- package/dist/admin/surface/goals/goals.css +10 -13
- package/dist/admin/surface/home.model.d.ts +8 -8
- package/dist/admin/surface/home.model.js +23 -12
- package/dist/admin/surface/newTest/NewTest.js +9 -9
- package/dist/admin/surface/pageBlock/parts.js +31 -26
- package/dist/admin/surface/readiness/ActionDialogs.js +47 -19
- package/dist/admin/surface/readiness/Banner.d.ts +3 -0
- package/dist/admin/surface/readiness/Banner.js +58 -33
- 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 +25 -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 +11 -1
- package/dist/admin/surface/results/ResultsBody.js +277 -105
- 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.d.ts +3 -2
- package/dist/admin/surface/results/recommendation.model.js +12 -9
- 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 +61 -47
- package/dist/admin/surface/test/AdvancedSection.js +55 -48
- package/dist/admin/surface/test/GoalSection.js +46 -47
- 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 -68
- 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/test/testPage.model.d.ts +2 -3
- package/dist/admin/surface/test/testPage.model.js +1 -21
- package/dist/admin/surface/useTestDocument.js +12 -5
- 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/useLayerUsage.js +14 -23
- package/dist/admin/views/ExperimentsRootView.js +12 -2
- 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/collections/experiments/config.js +25 -0
- package/dist/collections/experiments/lockHook.js +3 -1
- 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/contentDone.d.ts +4 -0
- package/dist/contentDone.js +5 -0
- package/dist/db/appends.d.ts +6 -2
- package/dist/db/appends.js +10 -4
- package/dist/db/identities.d.ts +8 -7
- package/dist/db/identities.js +9 -6
- package/dist/db/locks.d.ts +6 -0
- package/dist/db/locks.js +13 -1
- 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/pageOccupancy.d.ts +12 -0
- package/dist/db/pageOccupancy.js +45 -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 +25 -8
- package/dist/db/sweep.js +11 -6
- package/dist/endpoints/index.js +19 -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/adminRsc.d.ts +1 -0
- package/dist/exports/adminRsc.js +1 -0
- package/dist/exports/index.d.ts +2 -1
- package/dist/exports/index.js +1 -0
- package/dist/exports/puck.d.ts +2 -1
- package/dist/exports/puck.js +1 -1
- package/dist/exports/puckEditor.d.ts +4 -1
- package/dist/exports/puckEditor.js +3 -1
- package/dist/exports/rsc.d.ts +6 -1
- package/dist/exports/rsc.js +2 -0
- package/dist/hooks/pageGuard.d.ts +5 -0
- package/dist/hooks/pageGuard.js +139 -0
- package/dist/lib/fetchAllDocs.d.ts +16 -0
- package/dist/lib/fetchAllDocs.js +56 -0
- package/dist/log.js +6 -3
- package/dist/manifest/compile.d.ts +5 -0
- package/dist/manifest/compile.js +5 -1
- package/dist/options.js +16 -0
- package/dist/plugin.js +30 -2
- 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 -5
- package/dist/puck/copy.js +6 -5
- 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 +121 -0
- package/dist/puck/editor/editor.css +288 -204
- package/dist/puck/editor/editor.model.d.ts +4 -1
- package/dist/puck/editor/editor.model.js +10 -7
- package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
- package/dist/puck/editor/experimentOutlinePlugin.js +89 -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/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/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/sanitizeReadinessDetail.d.ts +7 -0
- package/dist/sanitizeReadinessDetail.js +63 -0
- package/dist/server/linkIdentity.d.ts +10 -1
- package/dist/server/linkIdentity.js +17 -15
- package/dist/server/metadata.d.ts +5 -0
- package/dist/server/metadata.js +18 -3
- package/dist/server/normaliseContentRelationships.d.ts +7 -0
- package/dist/server/normaliseContentRelationships.js +62 -0
- package/dist/server/recordContentChange.d.ts +1 -1
- package/dist/server/recordContentChange.js +17 -8
- package/dist/server/runResults.d.ts +3 -2
- package/dist/server/runResults.js +157 -35
- 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 +10 -3
- package/dist/transitions/checkStart.js +25 -0
- package/dist/transitions/contentFields.d.ts +7 -0
- package/dist/transitions/contentFields.js +20 -0
- package/dist/transitions/decide.js +22 -2
- 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 +3 -6
- package/dist/transitions/placements.js +12 -1
- package/dist/transitions/preflightData.d.ts +9 -1
- package/dist/transitions/preflightData.js +138 -20
- 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 +62 -7
- package/dist/types/contracts.d.ts +24 -4
- 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 +16 -0
- package/dist/ui/Tabs.js +61 -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 +40 -0
- package/dist/ui/accent.js +139 -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 +5 -2
- 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 -70
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { fetchAllDocs } from '../../lib/fetchAllDocs.js';
|
|
3
4
|
import { Banner, Button, SelectInput, useConfig, useField } from '@payloadcms/ui';
|
|
4
5
|
import { useEffect, useState } from 'react';
|
|
5
6
|
import { bindArmSelect, experimentId } from './armSelect.model.js';
|
|
6
7
|
import { ARM_SELECT_COPY } from '../copy/armSelect.js';
|
|
8
|
+
/** Only authored copy reaches the field; transport or parse errors read as unavailable. */ const knownCopy = (reason)=>reason instanceof Error && Object.values(ARM_SELECT_COPY).includes(reason.message) ? reason.message : ARM_SELECT_COPY.unavailable;
|
|
7
9
|
export function ArmSelectField({ path, field, readOnly }) {
|
|
8
10
|
const { config } = useConfig();
|
|
9
11
|
const api = config.routes.api;
|
|
@@ -26,27 +28,17 @@ export function ArmSelectField({ path, field, readOnly }) {
|
|
|
26
28
|
if (!point) return;
|
|
27
29
|
const controller = new AbortController();
|
|
28
30
|
async function list(statuses) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
credentials: 'include',
|
|
41
|
-
signal: controller.signal
|
|
42
|
-
});
|
|
43
|
-
if (!response.ok) throw new Error(response.status === 403 ? ARM_SELECT_COPY.forbidden : ARM_SELECT_COPY.unavailable);
|
|
44
|
-
const data = await response.json();
|
|
45
|
-
docs.push(...data.docs);
|
|
46
|
-
if (!data.hasNextPage) break;
|
|
47
|
-
page++;
|
|
48
|
-
}
|
|
49
|
-
return docs;
|
|
31
|
+
const query = new URLSearchParams({
|
|
32
|
+
depth: '0',
|
|
33
|
+
'where[flagPoint][equals]': point,
|
|
34
|
+
'where[status][in]': statuses
|
|
35
|
+
});
|
|
36
|
+
const response = await fetchAllDocs(`${api}/xp-experiments?${query}`, {
|
|
37
|
+
credentials: 'include',
|
|
38
|
+
signal: controller.signal
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) throw new Error(response.status === 403 ? ARM_SELECT_COPY.forbidden : ARM_SELECT_COPY.unavailable);
|
|
41
|
+
return response.docs;
|
|
50
42
|
}
|
|
51
43
|
void Promise.all([
|
|
52
44
|
list('running,decided'),
|
|
@@ -63,7 +55,7 @@ export function ArmSelectField({ path, field, readOnly }) {
|
|
|
63
55
|
}).catch((reason)=>{
|
|
64
56
|
if (!controller.signal.aborted) setError({
|
|
65
57
|
key,
|
|
66
|
-
message: reason
|
|
58
|
+
message: knownCopy(reason)
|
|
67
59
|
});
|
|
68
60
|
});
|
|
69
61
|
return ()=>controller.abort();
|
|
@@ -1,21 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Arm, ArmKey, ExperimentKind } from '../../types/contracts.js';
|
|
3
|
-
export declare function defaultArms(kind?: ExperimentKind): Arm[];
|
|
4
|
-
/** Keep content slots in step with the experiment kind, including an unset kind. */
|
|
5
|
-
export declare function withSlotsFor(arms: Arm[], kind?: ExperimentKind): Arm[];
|
|
6
|
-
export declare function totalWeight(arms: Arm[]): number;
|
|
7
|
-
export declare function addVariant(arms: Arm[], kind?: ExperimentKind): Arm[];
|
|
8
|
-
export declare function removeArm(arms: Arm[], key: ArmKey): Arm[];
|
|
9
|
-
export declare function addHoldoutArm(arms: Arm[]): Arm[];
|
|
10
|
-
export declare function removeHoldout(arms: Arm[]): Arm[];
|
|
11
|
-
export declare function splitEvenly(arms: Arm[]): Arm[];
|
|
12
|
-
export declare function setWeight(arms: Arm[], key: ArmKey, weight: number): Arm[];
|
|
13
|
-
export declare function issuesByRow(arms: Arm[], kind?: ExperimentKind): {
|
|
14
|
-
rows: ArmIssue[][];
|
|
15
|
-
form: ArmIssue[];
|
|
16
|
-
};
|
|
17
|
-
export declare const ARM_FIELDS: readonly ["key", "label", "weight", "kind", "slot", "params", "pageTarget"];
|
|
18
|
-
/** Payload arrays store each row's values at separate field paths. */
|
|
19
|
-
export declare function readArms(fields: Record<string, {
|
|
20
|
-
value?: unknown;
|
|
21
|
-
}>, path?: string): Arm[];
|
|
1
|
+
export * from '../../arms/model.js';
|
|
@@ -1,153 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { GROUP_LABELS } from '../copy/arms.js';
|
|
3
|
-
const VARIANTS = [
|
|
4
|
-
'variantA',
|
|
5
|
-
'variantB',
|
|
6
|
-
'variantC'
|
|
7
|
-
];
|
|
8
|
-
export function defaultArms(kind) {
|
|
9
|
-
return withSlotsFor([
|
|
10
|
-
{
|
|
11
|
-
key: 'control',
|
|
12
|
-
kind: 'control',
|
|
13
|
-
label: GROUP_LABELS.control,
|
|
14
|
-
weight: 50
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
key: 'variantA',
|
|
18
|
-
kind: 'variant',
|
|
19
|
-
label: GROUP_LABELS.variantA,
|
|
20
|
-
weight: 50
|
|
21
|
-
}
|
|
22
|
-
], kind);
|
|
23
|
-
}
|
|
24
|
-
/** Keep content slots in step with the experiment kind, including an unset kind. */ export function withSlotsFor(arms, kind) {
|
|
25
|
-
const next = arms.map((arm)=>{
|
|
26
|
-
if (kind === 'block' && arm.kind !== 'holdout') return arm.slot === arm.key ? arm : {
|
|
27
|
-
...arm,
|
|
28
|
-
slot: arm.key
|
|
29
|
-
};
|
|
30
|
-
// Postgres returns null for unset columns; null counts as "no slot" so a loaded doc is not rewritten.
|
|
31
|
-
if (arm.slot == null) return arm;
|
|
32
|
-
const { slot: _slot, ...rest } = arm;
|
|
33
|
-
return rest;
|
|
34
|
-
});
|
|
35
|
-
return next.every((arm, index)=>arm === arms[index]) ? arms : next;
|
|
36
|
-
}
|
|
37
|
-
const finiteWeight = (weight)=>Number.isFinite(weight) ? weight : 0;
|
|
38
|
-
export function totalWeight(arms) {
|
|
39
|
-
return arms.reduce((sum, arm)=>sum + finiteWeight(arm.weight), 0);
|
|
40
|
-
}
|
|
41
|
-
export function addVariant(arms, kind) {
|
|
42
|
-
if (!arms.some((arm)=>arm.kind === 'control')) return arms;
|
|
43
|
-
const key = VARIANTS.find((candidate)=>!arms.some((arm)=>arm.key === candidate));
|
|
44
|
-
if (!key || arms.filter((arm)=>arm.kind === 'variant').length >= 3) return arms;
|
|
45
|
-
const block = kind === 'block' || kind === undefined && arms.some((arm)=>arm.slot !== undefined);
|
|
46
|
-
const variant = {
|
|
47
|
-
key,
|
|
48
|
-
kind: 'variant',
|
|
49
|
-
label: GROUP_LABELS[key],
|
|
50
|
-
weight: 0,
|
|
51
|
-
...block ? {
|
|
52
|
-
slot: key
|
|
53
|
-
} : {}
|
|
54
|
-
};
|
|
55
|
-
const index = arms.findIndex((arm)=>arm.kind === 'holdout');
|
|
56
|
-
const result = [
|
|
57
|
-
...arms
|
|
58
|
-
];
|
|
59
|
-
result.splice(index < 0 ? arms.length : index, 0, variant);
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
export function removeArm(arms, key) {
|
|
63
|
-
if (arms.some((arm)=>arm.key === key && arm.kind === 'control')) return arms;
|
|
64
|
-
if (key === 'holdout') return removeHoldout(arms);
|
|
65
|
-
return arms.filter((arm)=>arm.key !== key);
|
|
66
|
-
}
|
|
67
|
-
export function addHoldoutArm(arms) {
|
|
68
|
-
if (!arms.some((arm)=>arm.kind === 'control') || arms.some((arm)=>arm.kind === 'holdout' || arm.key === 'holdout')) return arms;
|
|
69
|
-
return addHoldout(arms.map((arm)=>Number.isFinite(arm.weight) ? arm : {
|
|
70
|
-
...arm,
|
|
71
|
-
weight: 0
|
|
72
|
-
}));
|
|
73
|
-
}
|
|
74
|
-
// Largest remainder, with equal fractional remainders awarded to Original first.
|
|
75
|
-
function distribute(arms, total, proportional) {
|
|
76
|
-
if (!arms.length) return arms;
|
|
77
|
-
const weightTotal = totalWeight(arms);
|
|
78
|
-
const useWeights = proportional && weightTotal > 0;
|
|
79
|
-
const denominator = useWeights ? weightTotal : arms.length;
|
|
80
|
-
const numerators = arms.map((arm)=>(useWeights ? finiteWeight(arm.weight) : 1) * total);
|
|
81
|
-
const weights = numerators.map((numerator)=>Math.floor(numerator / denominator));
|
|
82
|
-
const order = arms.map((_, i)=>i).sort((a, b)=>numerators[b] % denominator - numerators[a] % denominator || Number(arms[b].kind === 'control') - Number(arms[a].kind === 'control') || a - b);
|
|
83
|
-
const remainder = total - weights.reduce((sum, weight)=>sum + weight, 0);
|
|
84
|
-
for(let i = 0; i < remainder; i++)weights[order[i % order.length]]++;
|
|
85
|
-
return arms.map((arm, i)=>({
|
|
86
|
-
...arm,
|
|
87
|
-
weight: weights[i]
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
export function removeHoldout(arms) {
|
|
91
|
-
if (!arms.some((arm)=>arm.kind === 'holdout')) return arms;
|
|
92
|
-
return distribute(arms.filter((arm)=>arm.kind !== 'holdout'), totalWeight(arms), true);
|
|
93
|
-
}
|
|
94
|
-
export function splitEvenly(arms) {
|
|
95
|
-
const holdout = arms.find((arm)=>arm.kind === 'holdout');
|
|
96
|
-
const weight = finiteWeight(holdout?.weight ?? 0);
|
|
97
|
-
return [
|
|
98
|
-
...distribute(arms.filter((arm)=>arm.kind !== 'holdout'), 100 - weight, false),
|
|
99
|
-
...holdout ? [
|
|
100
|
-
{
|
|
101
|
-
...holdout,
|
|
102
|
-
weight
|
|
103
|
-
}
|
|
104
|
-
] : []
|
|
105
|
-
];
|
|
106
|
-
}
|
|
107
|
-
export function setWeight(arms, key, weight) {
|
|
108
|
-
return arms.map((arm)=>arm.key === key ? {
|
|
109
|
-
...arm,
|
|
110
|
-
weight
|
|
111
|
-
} : arm);
|
|
112
|
-
}
|
|
113
|
-
export function issuesByRow(arms, kind) {
|
|
114
|
-
const rows = arms.map(()=>[]);
|
|
115
|
-
const form = [];
|
|
116
|
-
for (const issue of checkArms(arms, kind)){
|
|
117
|
-
const index = issue.armIndex ?? arms.findIndex((arm)=>arm.key === issue.armKey);
|
|
118
|
-
if (index >= 0 && index < arms.length) rows[index].push(issue);
|
|
119
|
-
else form.push(issue);
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
rows,
|
|
123
|
-
form
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
export const ARM_FIELDS = [
|
|
127
|
-
'key',
|
|
128
|
-
'label',
|
|
129
|
-
'weight',
|
|
130
|
-
'kind',
|
|
131
|
-
'slot',
|
|
132
|
-
'params',
|
|
133
|
-
'pageTarget'
|
|
134
|
-
];
|
|
135
|
-
/** Payload arrays store each row's values at separate field paths. */ export function readArms(fields, path = 'arms') {
|
|
136
|
-
const indices = new Set();
|
|
137
|
-
for (const name of Object.keys(fields)){
|
|
138
|
-
if (!name.startsWith(`${path}.`)) continue;
|
|
139
|
-
const [index, field] = name.slice(path.length + 1).split('.');
|
|
140
|
-
if (/^\d+$/.test(index) && field === 'key') indices.add(Number(index));
|
|
141
|
-
}
|
|
142
|
-
return [
|
|
143
|
-
...indices
|
|
144
|
-
].sort((a, b)=>a - b).map((index)=>Object.fromEntries(ARM_FIELDS.flatMap((field)=>{
|
|
145
|
-
const value = fields[`${path}.${index}.${field}`]?.value;
|
|
146
|
-
return value === undefined ? [] : [
|
|
147
|
-
[
|
|
148
|
-
field,
|
|
149
|
-
value
|
|
150
|
-
]
|
|
151
|
-
];
|
|
152
|
-
})));
|
|
153
|
-
}
|
|
1
|
+
export * from '../../arms/model.js';
|
|
@@ -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,6 +1,5 @@
|
|
|
1
|
-
import type { ArmIssueCode } from '../../types/endpoints.js';
|
|
2
1
|
export { ARM_LABELS as GROUP_LABELS } from '../../labels/arms.js';
|
|
3
|
-
export
|
|
2
|
+
export { ISSUE_COPY, issueMessage } from '../../arms/copy.js';
|
|
4
3
|
export declare const ARMS_COPY: {
|
|
5
4
|
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.";
|
|
6
5
|
readonly pageRequired: "Enter a page path for this group.";
|
|
@@ -12,7 +11,3 @@ export declare const ARMS_COPY: {
|
|
|
12
11
|
readonly paramOption: "Choose an available option for {label}.";
|
|
13
12
|
readonly paramCheckbox: "Choose yes or no for {label}.";
|
|
14
13
|
};
|
|
15
|
-
export declare function issueMessage(issue: {
|
|
16
|
-
code: string;
|
|
17
|
-
message: string;
|
|
18
|
-
}, total: number): string;
|
package/dist/admin/copy/arms.js
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
export { ARM_LABELS as GROUP_LABELS } from '../../labels/arms.js';
|
|
2
|
-
export
|
|
3
|
-
'weights-total': 'Weights must add up to 100%. They add up to {total}% now.',
|
|
4
|
-
'control-count': 'Include exactly one Original.',
|
|
5
|
-
'control-first': 'Put the Original first.',
|
|
6
|
-
'holdout-last': 'Put the holdout last.',
|
|
7
|
-
'holdout-count': 'Include only one holdout.',
|
|
8
|
-
'variant-count': 'Include up to three variants.',
|
|
9
|
-
'duplicate-key': 'Each group must have a different standard name.',
|
|
10
|
-
'duplicate-slot': 'Each group must have a different content slot.',
|
|
11
|
-
'slot-required': 'Choose the matching content slot for this group.',
|
|
12
|
-
'invalid-key': 'Use the standard group names: Original, Variant A–C, Holdout.'
|
|
13
|
-
};
|
|
2
|
+
export { ISSUE_COPY, issueMessage } from '../../arms/copy.js';
|
|
14
3
|
export const ARMS_COPY = {
|
|
15
4
|
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.',
|
|
16
5
|
pageRequired: 'Enter a page path for this group.',
|
|
@@ -22,7 +11,3 @@ export const ARMS_COPY = {
|
|
|
22
11
|
paramOption: 'Choose an available option for {label}.',
|
|
23
12
|
paramCheckbox: 'Choose yes or no for {label}.'
|
|
24
13
|
};
|
|
25
|
-
export function issueMessage(issue, total) {
|
|
26
|
-
const copy = Object.hasOwn(ISSUE_COPY, issue.code) ? ISSUE_COPY[issue.code] : issue.message;
|
|
27
|
-
return copy.replace('{total}', String(total));
|
|
28
|
-
}
|
|
@@ -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
|
}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
export declare const GOALS_COPY: {
|
|
2
2
|
readonly title: "Goals";
|
|
3
|
+
readonly goal: "Goal";
|
|
3
4
|
readonly newGoal: "New goal";
|
|
4
5
|
readonly name: "Name";
|
|
5
6
|
readonly source: "Measured by";
|
|
6
7
|
readonly directionColumn: "Better when";
|
|
7
8
|
readonly direction: "Better when it goes";
|
|
8
|
-
readonly increase: "
|
|
9
|
-
readonly decrease: "
|
|
9
|
+
readonly increase: "More is better";
|
|
10
|
+
readonly decrease: "Less is better";
|
|
10
11
|
readonly usedBy: "Used by";
|
|
11
12
|
readonly status: "Status";
|
|
12
13
|
readonly inUse: "In use";
|
|
13
|
-
readonly unused: "Not used";
|
|
14
|
-
readonly empty: "A goal is
|
|
14
|
+
readonly unused: "Not used yet";
|
|
15
|
+
readonly empty: "No goals yet. A goal is what a test tries to improve.";
|
|
15
16
|
readonly loading: "Loading goals…";
|
|
16
17
|
readonly loadError: "Couldn't load goals. Try again.";
|
|
17
18
|
readonly retry: "Try again";
|
|
18
19
|
readonly slug: "Short name";
|
|
19
20
|
readonly slugHelp: "A unique short name for this goal.";
|
|
20
21
|
readonly slugLocked: "The short name stays the same once a test uses this goal.";
|
|
22
|
+
readonly sourceLocked: "What this goal measures stays the same once a test uses it.";
|
|
21
23
|
readonly chooseSource: "Choose an outcome";
|
|
22
24
|
readonly window: "Count results for (hours)";
|
|
23
25
|
readonly minimum: "Minimum completions before deciding";
|
|
@@ -33,12 +35,25 @@ export declare const GOALS_COPY: {
|
|
|
33
35
|
readonly save: "Save";
|
|
34
36
|
readonly saving: "Saving…";
|
|
35
37
|
readonly cancel: "Cancel";
|
|
36
|
-
readonly remove: "Delete";
|
|
38
|
+
readonly remove: "Delete goal";
|
|
37
39
|
readonly thresholdError: "Enter a percentage of zero or more, or leave blank for 5%.";
|
|
38
40
|
readonly deleting: "Deleting…";
|
|
39
41
|
readonly saveError: "Couldn't save the goal. Try again.";
|
|
40
42
|
readonly deleteError: "Couldn't delete the goal. Try again.";
|
|
41
43
|
readonly breadcrumb: "Breadcrumb";
|
|
42
|
-
readonly missing: "This goal
|
|
44
|
+
readonly missing: "This goal no longer exists.";
|
|
45
|
+
readonly notFound: "This goal no longer exists.";
|
|
46
|
+
readonly backToTest: "Back to the test";
|
|
47
|
+
readonly useGoal: "Use this goal";
|
|
48
|
+
readonly open: "Open";
|
|
49
|
+
readonly search: "Search goals";
|
|
50
|
+
readonly clearSearch: "Clear search";
|
|
51
|
+
readonly fields: "Goal details";
|
|
52
|
+
readonly deleteTitle: "Delete this goal?";
|
|
53
|
+
readonly deleteBody: "This cannot be undone.";
|
|
54
|
+
readonly confirmDelete: "Delete";
|
|
43
55
|
};
|
|
44
56
|
export declare function goalUsedBy(count: number): string;
|
|
57
|
+
export declare function goalInUse(count: number): string;
|
|
58
|
+
export declare function goalDeleteLocked(count: number): string;
|
|
59
|
+
export declare function goalsNoMatches(search: string): string;
|