@delmaredigital/payload-experiments 0.3.0-rc.1 → 0.4.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -21
- package/dist/admin/MetricSourceSelect.js +3 -3
- package/dist/admin/PlacementStatus.d.ts +3 -0
- package/dist/admin/PlacementStatus.js +54 -0
- package/dist/admin/PreviewLinks.d.ts +2 -1
- package/dist/admin/PreviewLinks.js +47 -26
- package/dist/admin/TestsNavLink.d.ts +3 -1
- package/dist/admin/TestsNavLink.js +2 -2
- package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
- package/dist/admin/actions/ConfirmDialog.js +2 -2
- package/dist/admin/actions/DecideDialog.d.ts +2 -1
- package/dist/admin/actions/DecideDialog.js +2 -2
- package/dist/admin/api.d.ts +11 -2
- package/dist/admin/api.js +20 -2
- package/dist/admin/armSelect/ArmSelectField.js +14 -22
- package/dist/admin/arms/arms.model.d.ts +1 -21
- package/dist/admin/arms/arms.model.js +1 -153
- package/dist/admin/blocks/BlockPanel.d.ts +4 -0
- package/dist/admin/blocks/BlockPanel.js +153 -0
- package/dist/admin/blocks/TestBlockField.d.ts +5 -0
- package/dist/admin/blocks/TestBlockField.js +163 -0
- package/dist/admin/blocks/formApi.d.ts +43 -0
- package/dist/admin/blocks/formApi.js +153 -0
- package/dist/admin/blocks/inject.d.ts +7 -0
- package/dist/admin/blocks/inject.js +41 -0
- package/dist/admin/blocks/model.d.ts +17 -0
- package/dist/admin/blocks/model.js +45 -0
- package/dist/admin/blocks/statusCache.d.ts +7 -0
- package/dist/admin/blocks/statusCache.js +83 -0
- package/dist/admin/blocks/useBlockForm.d.ts +17 -0
- package/dist/admin/blocks/useBlockForm.js +46 -0
- package/dist/admin/copy/actions.d.ts +2 -0
- package/dist/admin/copy/actions.js +9 -0
- package/dist/admin/copy/arms.d.ts +1 -6
- package/dist/admin/copy/arms.js +1 -16
- package/dist/admin/copy/blocks.d.ts +27 -0
- package/dist/admin/copy/blocks.js +38 -0
- package/dist/admin/copy/goals.d.ts +2 -0
- package/dist/admin/copy/goals.js +4 -1
- package/dist/admin/copy/newTest.d.ts +10 -0
- package/dist/admin/copy/newTest.js +10 -0
- package/dist/admin/copy/pages.d.ts +26 -0
- package/dist/admin/copy/pages.js +26 -0
- package/dist/admin/copy/readiness.d.ts +25 -3
- package/dist/admin/copy/readiness.js +25 -3
- package/dist/admin/copy/results.d.ts +3 -0
- package/dist/admin/copy/results.js +4 -1
- package/dist/admin/copy/review.d.ts +1 -1
- package/dist/admin/copy/review.js +1 -1
- package/dist/admin/copy/surface.d.ts +0 -1
- package/dist/admin/copy/surface.js +0 -1
- package/dist/admin/copy/testPage.d.ts +4 -0
- package/dist/admin/copy/testPage.js +4 -0
- package/dist/admin/goals/XpGoalField.d.ts +2 -0
- package/dist/admin/goals/XpGoalField.js +152 -0
- package/dist/admin/metricSource.model.d.ts +2 -0
- package/dist/admin/metricSource.model.js +4 -0
- package/dist/admin/pageCollections.d.ts +10 -0
- package/dist/admin/pageCollections.js +20 -0
- package/dist/admin/paths.d.ts +8 -1
- package/dist/admin/paths.js +8 -1
- package/dist/admin/placementStatus.model.d.ts +2 -0
- package/dist/admin/placementStatus.model.js +19 -0
- package/dist/admin/placements/placementLabels.d.ts +11 -0
- package/dist/admin/placements/placementLabels.js +21 -0
- package/dist/admin/placements/placementLabels.model.d.ts +13 -0
- package/dist/admin/placements/placementLabels.model.js +12 -0
- package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
- package/dist/admin/placements/usePlacementLabels.js +102 -0
- package/dist/admin/previewLinks.model.d.ts +2 -0
- package/dist/admin/previewLinks.model.js +3 -0
- package/dist/admin/results/DataQualityPanel.js +4 -1
- package/dist/admin/results/legacy.d.ts +16 -0
- package/dist/admin/results/legacy.js +27 -0
- package/dist/admin/results/results.model.d.ts +9 -6
- package/dist/admin/results/results.model.js +22 -9
- package/dist/admin/results/snapshot.d.ts +14 -0
- package/dist/admin/results/snapshot.js +3 -0
- package/dist/admin/surface/TestsHome.js +43 -15
- package/dist/admin/surface/TestsSurface.js +5 -1
- package/dist/admin/surface/contracts.d.ts +6 -5
- package/dist/admin/surface/goals/GoalForm.js +67 -34
- package/dist/admin/surface/goals/GoalsList.js +3 -3
- package/dist/admin/surface/goals/data.js +19 -16
- package/dist/admin/surface/goals/goals.model.d.ts +1 -2
- package/dist/admin/surface/goals/goals.model.js +1 -3
- package/dist/admin/surface/home.model.d.ts +2 -1
- package/dist/admin/surface/home.model.js +8 -4
- package/dist/admin/surface/newTest/NewTest.js +58 -17
- package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
- package/dist/admin/surface/newTest/PageWizard.js +209 -0
- package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
- package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
- package/dist/admin/surface/pagePicker/PagePicker.d.ts +10 -0
- package/dist/admin/surface/pagePicker/PagePicker.js +172 -0
- package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +25 -0
- package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
- package/dist/admin/surface/readiness/ActionDialogs.js +103 -69
- package/dist/admin/surface/readiness/Banner.js +30 -6
- package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
- package/dist/admin/surface/readiness/readiness.model.js +95 -6
- package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
- package/dist/admin/surface/results/ReleasePages.js +3 -7
- package/dist/admin/surface/results/ResultsBody.js +23 -3
- package/dist/admin/surface/results/ResultsHeader.js +2 -5
- package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
- package/dist/admin/surface/results/recommendation.model.js +10 -3
- package/dist/admin/surface/results/useResults.d.ts +10 -1
- package/dist/admin/surface/surface.css +0 -1
- package/dist/admin/surface/test/AdvancedSection.js +111 -104
- package/dist/admin/surface/test/GoalSection.js +97 -22
- package/dist/admin/surface/test/LengthSection.js +1 -1
- package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
- package/dist/admin/surface/test/PageHoldout.js +67 -0
- package/dist/admin/surface/test/TestPage.js +2 -1
- package/dist/admin/surface/test/VersionsSection.js +12 -5
- package/dist/admin/surface/test/fields.d.ts +13 -4
- package/dist/admin/surface/test/fields.js +46 -14
- package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
- package/dist/admin/surface/test/jumpToSection.js +4 -3
- package/dist/admin/surface/test/pageParts.d.ts +4 -1
- package/dist/admin/surface/test/pageParts.js +79 -2
- package/dist/admin/surface/test/testPage.css +8 -1
- package/dist/admin/surface/test/testPage.model.d.ts +2 -3
- package/dist/admin/surface/test/testPage.model.js +1 -21
- package/dist/admin/surface/testDocument.model.d.ts +1 -0
- package/dist/admin/surface/testDocument.model.js +2 -1
- package/dist/admin/surface/useTestDocument.d.ts +11 -2
- package/dist/admin/surface/useTestDocument.js +55 -16
- package/dist/admin/useApi.d.ts +11 -2
- package/dist/admin/useApi.js +3 -5
- package/dist/admin/useLayerUsage.js +14 -23
- package/dist/admin/views/ExperimentsRootView.js +58 -16
- package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
- package/dist/admin/views/TestsNavLinkServer.js +43 -0
- package/dist/arms/copy.d.ts +6 -0
- package/dist/arms/copy.js +16 -0
- package/dist/arms/model.d.ts +23 -0
- package/dist/arms/model.js +173 -0
- package/dist/blocks/bindings.d.ts +3 -0
- package/dist/blocks/bindings.js +34 -0
- package/dist/blocks/createExperimentBlock.d.ts +6 -0
- package/dist/blocks/createExperimentBlock.js +77 -0
- package/dist/blocks/formState.d.ts +31 -0
- package/dist/blocks/formState.js +146 -0
- package/dist/blocks/model.d.ts +41 -0
- package/dist/blocks/model.js +34 -0
- package/dist/client/ExperimentGroup.d.ts +6 -0
- package/dist/client/ExperimentGroup.js +9 -0
- package/dist/client/ExperimentsProvider.js +20 -4
- package/dist/client/clickGoals.d.ts +4 -0
- package/dist/client/clickGoals.js +43 -0
- package/dist/collections/experiments/config.js +12 -2
- package/dist/collections/experiments/deleteGuard.d.ts +2 -1
- package/dist/collections/experiments/deleteGuard.js +12 -1
- package/dist/collections/experiments/editability.js +4 -2
- package/dist/collections/experiments/pickSlug.d.ts +2 -0
- package/dist/collections/experiments/pickSlug.js +8 -0
- package/dist/collections/labels.d.ts +3 -0
- package/dist/collections/labels.js +20 -0
- package/dist/collections/metrics.js +12 -2
- package/dist/collections/phases.js +1 -0
- package/dist/collections/serialize.js +7 -0
- package/dist/db/appends.d.ts +6 -2
- package/dist/db/appends.js +10 -4
- package/dist/db/clickGoals.d.ts +9 -0
- package/dist/db/clickGoals.js +22 -0
- package/dist/db/delete.d.ts +1 -0
- package/dist/db/delete.js +1 -0
- package/dist/db/locks.d.ts +14 -0
- package/dist/db/locks.js +36 -1
- package/dist/db/names.d.ts +4 -0
- package/dist/db/names.js +4 -0
- package/dist/db/pageBindings.d.ts +4 -0
- package/dist/db/pageBindings.js +17 -0
- package/dist/db/pageBusy.d.ts +2 -0
- package/dist/db/pageBusy.js +10 -0
- package/dist/db/pageContent.d.ts +10 -0
- package/dist/db/pageContent.js +79 -0
- package/dist/db/pageOccupancy.d.ts +12 -0
- package/dist/db/pageOccupancy.js +45 -0
- package/dist/db/placements.d.ts +33 -0
- package/dist/db/placements.js +54 -0
- package/dist/db/slugs.d.ts +8 -0
- package/dist/db/slugs.js +40 -0
- package/dist/db/snapshots.d.ts +6 -0
- package/dist/db/snapshots.js +9 -5
- package/dist/endpoints/clickKeys.d.ts +5 -0
- package/dist/endpoints/clickKeys.js +17 -0
- package/dist/endpoints/events.js +4 -1
- package/dist/endpoints/index.js +28 -0
- package/dist/endpoints/pages.d.ts +5 -0
- package/dist/endpoints/pages.js +120 -0
- package/dist/endpoints/placementStatus.d.ts +6 -0
- package/dist/endpoints/placementStatus.js +238 -0
- package/dist/endpoints/readBody.d.ts +2 -0
- package/dist/endpoints/readBody.js +24 -0
- package/dist/endpoints/results.js +5 -1
- package/dist/endpoints/summary.js +11 -4
- package/dist/endpoints/wrapBlock.d.ts +6 -0
- package/dist/endpoints/wrapBlock.js +76 -0
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +4 -4
- package/dist/events/rows.d.ts +1 -1
- package/dist/events/rows.js +5 -3
- package/dist/exports/adminClient.d.ts +5 -0
- package/dist/exports/adminClient.js +5 -0
- package/dist/exports/adminRsc.d.ts +1 -0
- package/dist/exports/adminRsc.js +1 -0
- package/dist/exports/blocks.d.ts +2 -0
- package/dist/exports/blocks.js +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +1 -0
- package/dist/exports/index.d.ts +6 -1
- package/dist/exports/index.js +3 -0
- package/dist/exports/puck.d.ts +5 -2
- package/dist/exports/puck.js +3 -1
- package/dist/exports/puckEditor.d.ts +6 -1
- package/dist/exports/puckEditor.js +4 -1
- package/dist/exports/rsc.d.ts +4 -0
- package/dist/exports/rsc.js +2 -0
- package/dist/fields/xpGoal.d.ts +3 -0
- package/dist/fields/xpGoal.js +28 -0
- package/dist/globals/state.js +10 -0
- package/dist/goals/cache.d.ts +3 -0
- package/dist/goals/cache.js +35 -0
- package/dist/goals/fairness.d.ts +40 -0
- package/dist/goals/fairness.js +200 -0
- package/dist/goals/key.d.ts +4 -0
- package/dist/goals/key.js +3 -0
- package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
- package/dist/goals/picker/clickGoalPicker.model.js +45 -0
- package/dist/goals/picker/copy.d.ts +16 -0
- package/dist/goals/picker/copy.js +15 -0
- package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
- package/dist/goals/picker/useClickGoalPicker.js +220 -0
- package/dist/hooks/pageGuard.d.ts +5 -1
- package/dist/hooks/pageGuard.js +160 -80
- package/dist/lib/fetchAllDocs.d.ts +16 -0
- package/dist/lib/fetchAllDocs.js +56 -0
- package/dist/onInit.js +29 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.js +43 -10
- package/dist/plugin.js +45 -28
- package/dist/puck/ExperimentPicker.js +47 -16
- package/dist/puck/ExperimentSlot.d.ts +51 -1
- package/dist/puck/ExperimentSlot.js +23 -12
- package/dist/puck/copy.d.ts +6 -4
- package/dist/puck/copy.js +6 -4
- package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
- package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
- package/dist/puck/editor/ResultsReadout.d.ts +6 -0
- package/dist/puck/editor/ResultsReadout.js +99 -0
- package/dist/puck/editor/VersionBuilder.d.ts +34 -0
- package/dist/puck/editor/VersionBuilder.js +317 -0
- package/dist/puck/editor/armWrites.d.ts +7 -0
- package/dist/puck/editor/armWrites.js +71 -0
- package/dist/puck/editor/armsCache.d.ts +14 -0
- package/dist/puck/editor/armsCache.js +78 -0
- package/dist/puck/editor/copy.d.ts +33 -1
- package/dist/puck/editor/copy.js +45 -8
- package/dist/puck/editor/createTest.d.ts +1 -1
- package/dist/puck/editor/createTest.js +25 -14
- package/dist/puck/editor/editor.chrome.css +48 -161
- package/dist/puck/editor/editor.css +48 -161
- package/dist/puck/editor/editor.model.d.ts +8 -3
- package/dist/puck/editor/editor.model.js +16 -10
- package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
- package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
- package/dist/puck/editor/integration.d.ts +10 -0
- package/dist/puck/editor/integration.js +30 -0
- package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
- package/dist/puck/editor/xpGoalPuckEditorField.js +134 -0
- package/dist/puck/experimentSlotField.d.ts +2 -0
- package/dist/puck/experimentSlotField.js +11 -3
- package/dist/puck/hostContext.d.ts +10 -0
- package/dist/puck/hostContext.js +12 -0
- package/dist/puck/integration.d.ts +6 -0
- package/dist/puck/integration.js +21 -0
- package/dist/puck/routes.d.ts +12 -0
- package/dist/puck/routes.js +11 -0
- package/dist/puck/slotConfig.d.ts +1 -1
- package/dist/puck/slotConfig.js +2 -2
- package/dist/puck/xpGoalField.d.ts +3 -0
- package/dist/puck/xpGoalField.js +6 -0
- package/dist/results/health.d.ts +4 -0
- package/dist/results/health.js +27 -0
- package/dist/results/snapshot.js +15 -2
- package/dist/sanitizeReadinessDetail.js +51 -1
- package/dist/server/metadata.js +9 -2
- package/dist/server/normaliseContentRelationships.d.ts +7 -0
- package/dist/server/normaliseContentRelationships.js +62 -0
- package/dist/server/pageKindSeoContract.d.ts +27 -0
- package/dist/server/pageKindSeoContract.js +80 -0
- package/dist/server/recordContentChange.d.ts +1 -1
- package/dist/server/recordContentChange.js +72 -13
- package/dist/server/resolveExperimentBlocks.d.ts +5 -0
- package/dist/server/resolveExperimentBlocks.js +54 -0
- package/dist/server/runResults.js +92 -19
- package/dist/server/toClientAssignments.js +2 -1
- package/dist/transitions/addMetric.d.ts +2 -1
- package/dist/transitions/addMetric.js +28 -1
- package/dist/transitions/checkStart.d.ts +17 -1
- package/dist/transitions/checkStart.js +99 -7
- package/dist/transitions/decide.js +40 -1
- package/dist/transitions/deleteExperiment.d.ts +3 -1
- package/dist/transitions/deleteExperiment.js +27 -1
- package/dist/transitions/kernel.js +4 -0
- package/dist/transitions/makeContent.d.ts +5 -0
- package/dist/transitions/makeContent.js +39 -11
- package/dist/transitions/pageCollection.d.ts +10 -0
- package/dist/transitions/pageCollection.js +20 -0
- package/dist/transitions/pageVersions.d.ts +10 -0
- package/dist/transitions/pageVersions.js +44 -0
- package/dist/transitions/phaseRecord.d.ts +1 -0
- package/dist/transitions/placementInteractions.d.ts +40 -0
- package/dist/transitions/placementInteractions.js +94 -0
- package/dist/transitions/placements.d.ts +10 -6
- package/dist/transitions/placements.js +156 -2
- package/dist/transitions/preflightData.d.ts +8 -0
- package/dist/transitions/preflightData.js +248 -36
- package/dist/transitions/resolveCaps.d.ts +1 -0
- package/dist/transitions/resolveCaps.js +6 -2
- package/dist/transitions/start.js +77 -3
- package/dist/types/admin.d.ts +1 -1
- package/dist/types/assignments.d.ts +1 -0
- package/dist/types/contracts.d.ts +31 -10
- package/dist/types/endpoints.d.ts +92 -5
- package/dist/types/index.d.ts +3 -0
- package/dist/types/puck.d.ts +7 -1
- package/dist/types/snapshot.d.ts +17 -4
- package/dist/types/surface.d.ts +13 -0
- package/dist/ui/Tabs.d.ts +4 -2
- package/dist/ui/Tabs.js +33 -26
- package/dist/ui/accent.d.ts +22 -3
- package/dist/ui/accent.js +74 -16
- package/package.json +12 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { createApi } from '../api.js';
|
|
4
|
+
import { placementDetail, placementDetailKey, preflightPlacementLabel, resolvedPlacementLabel } from './placementLabels.js';
|
|
5
|
+
export { placementDetailKey } from './placementLabels.js';
|
|
6
|
+
/** The view owns a cache, including misses, so repeated warnings never repeat reads. */ export function usePlacementLabels({ details, preflight, apiRoute, adminRoute, canManage, collectionTitles }) {
|
|
7
|
+
const titlesSignature = JSON.stringify(collectionTitles);
|
|
8
|
+
const cache = useMemo(()=>new Map(), [
|
|
9
|
+
apiRoute,
|
|
10
|
+
adminRoute,
|
|
11
|
+
titlesSignature
|
|
12
|
+
]);
|
|
13
|
+
const mounted = useRef(true);
|
|
14
|
+
useEffect(()=>{
|
|
15
|
+
mounted.current = true;
|
|
16
|
+
return ()=>{
|
|
17
|
+
mounted.current = false;
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
const [, refresh] = useState(0);
|
|
21
|
+
const signature = JSON.stringify(details.map(placementDetail));
|
|
22
|
+
useEffect(()=>{
|
|
23
|
+
if (!canManage || preflight) return;
|
|
24
|
+
const pending = [
|
|
25
|
+
...new Map(JSON.parse(signature).map((detail)=>[
|
|
26
|
+
placementDetailKey(detail),
|
|
27
|
+
detail
|
|
28
|
+
])).values()
|
|
29
|
+
].filter((detail)=>!cache.has(placementDetailKey(detail)));
|
|
30
|
+
const blocks = new Map();
|
|
31
|
+
const jobs = [];
|
|
32
|
+
const save = (value, label)=>{
|
|
33
|
+
cache.set(placementDetailKey(value), label);
|
|
34
|
+
};
|
|
35
|
+
for (const value of pending){
|
|
36
|
+
const detail = placementDetail(value);
|
|
37
|
+
// Reserve immediately: repeated renders do not issue duplicate requests.
|
|
38
|
+
save(value, resolvedPlacementLabel(value, undefined, adminRoute));
|
|
39
|
+
if (!detail.collection) continue;
|
|
40
|
+
if (detail.id != null) {
|
|
41
|
+
blocks.set(detail.collection, [
|
|
42
|
+
...blocks.get(detail.collection) ?? [],
|
|
43
|
+
value
|
|
44
|
+
]);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!detail.path) continue;
|
|
48
|
+
jobs.push(createApi(apiRoute).pages({
|
|
49
|
+
collection: detail.collection,
|
|
50
|
+
path: detail.path
|
|
51
|
+
}).then((result)=>{
|
|
52
|
+
if (result.ok) save(value, resolvedPlacementLabel(value, result.data?.docs[0], adminRoute));
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
for (const [collection, values] of blocks)jobs.push((async ()=>{
|
|
56
|
+
try {
|
|
57
|
+
const ids = [
|
|
58
|
+
...new Set(values.map((value)=>String(placementDetail(value).id)))
|
|
59
|
+
];
|
|
60
|
+
const query = new URLSearchParams({
|
|
61
|
+
'where[id][in]': ids.join(','),
|
|
62
|
+
depth: '0',
|
|
63
|
+
limit: String(ids.length)
|
|
64
|
+
});
|
|
65
|
+
const response = await fetch(`${apiRoute.replace(/\/$/, '')}/${encodeURIComponent(collection)}?${query}`, {
|
|
66
|
+
credentials: 'include'
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) return;
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
const titleField = collectionTitles?.[collection];
|
|
71
|
+
for (const value of values){
|
|
72
|
+
const doc = data.docs?.find((doc)=>String(doc.id) === String(placementDetail(value).id));
|
|
73
|
+
if (doc) save(value, resolvedPlacementLabel(value, {
|
|
74
|
+
id: placementDetail(value).id,
|
|
75
|
+
title: titleField && typeof doc[titleField] === 'string' ? doc[titleField] : undefined
|
|
76
|
+
}, adminRoute));
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
})());
|
|
80
|
+
void Promise.all(jobs).then(()=>{
|
|
81
|
+
if (mounted.current && jobs.length) refresh((value)=>value + 1);
|
|
82
|
+
});
|
|
83
|
+
}, [
|
|
84
|
+
signature,
|
|
85
|
+
preflight,
|
|
86
|
+
apiRoute,
|
|
87
|
+
adminRoute,
|
|
88
|
+
canManage,
|
|
89
|
+
collectionTitles,
|
|
90
|
+
cache
|
|
91
|
+
]);
|
|
92
|
+
return new Map(details.map((detail)=>{
|
|
93
|
+
const label = preflight ? preflightPlacementLabel(detail, preflight, adminRoute) : cache.get(placementDetailKey(detail)) ?? resolvedPlacementLabel(detail, undefined, adminRoute);
|
|
94
|
+
// Results-only readers may not be able to open the collection, so they get the name without a link.
|
|
95
|
+
return [
|
|
96
|
+
placementDetailKey(detail),
|
|
97
|
+
canManage ? label : {
|
|
98
|
+
title: label.title
|
|
99
|
+
}
|
|
100
|
+
];
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
@@ -6,4 +6,6 @@ type PreviewPage = Placement & {
|
|
|
6
6
|
};
|
|
7
7
|
/** Preview URLs identify pages; counts use the most placements in any one version. */
|
|
8
8
|
export declare function previewPages(placements: readonly Placement[], flagPoint?: string): PreviewPage[];
|
|
9
|
+
/** Pages that offer something to do: a preview or an editor link. The section is hidden when none do. */
|
|
10
|
+
export declare function actionablePreviewPages(placements: readonly Placement[], flagPoint?: string): PreviewPage[];
|
|
9
11
|
export {};
|
|
@@ -30,3 +30,6 @@
|
|
|
30
30
|
...pages.values()
|
|
31
31
|
].map(({ page })=>page);
|
|
32
32
|
}
|
|
33
|
+
/** Pages that offer something to do: a preview or an editor link. The section is hidden when none do. */ export function actionablePreviewPages(placements, flagPoint) {
|
|
34
|
+
return previewPages(placements, flagPoint).filter((page)=>page.url || page.editorUrl);
|
|
35
|
+
}
|
|
@@ -41,7 +41,10 @@ export function DataQualityPanel({ items, problem = false }) {
|
|
|
41
41
|
}) : null,
|
|
42
42
|
/*#__PURE__*/ _jsx("p", {
|
|
43
43
|
className: "xp-quality-next",
|
|
44
|
-
children: item.
|
|
44
|
+
children: item.href ? /*#__PURE__*/ _jsx("a", {
|
|
45
|
+
href: item.href,
|
|
46
|
+
children: item.nextStep
|
|
47
|
+
}) : item.nextStep
|
|
45
48
|
})
|
|
46
49
|
]
|
|
47
50
|
}, item.id)) : /*#__PURE__*/ _jsx("p", {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PhaseSummary } from '../../types/endpoints.js';
|
|
2
|
+
import type { ResultsContext } from './results.model.js';
|
|
3
|
+
import type { LegacySnapshot } from './snapshot.js';
|
|
4
|
+
/** Stands in for CORE-15a winnerArm, borrowing the live decision only for the newest rollout. */
|
|
5
|
+
export declare function legacyRolloutLine(snapshot: LegacySnapshot, phase: PhaseSummary, phases: PhaseSummary[], decision?: {
|
|
6
|
+
winnerArm?: string;
|
|
7
|
+
}): string;
|
|
8
|
+
/** Stands in for CORE-15a minExposuresPerArm; absent historical minima keep the old dash. */
|
|
9
|
+
export declare function legacyMinimum(snapshot: LegacySnapshot, ctx: ResultsContext): number | undefined;
|
|
10
|
+
/** Stands in for CORE-15a minExposuresPerArm by deriving the old notice from the frozen verdict. */
|
|
11
|
+
export declare function legacyTooFew(snapshot: LegacySnapshot): boolean;
|
|
12
|
+
/** Stands in for CORE-15a winnerArm in rollout recommendations using the old recommended arm and label. */
|
|
13
|
+
export declare function legacyRecommendationWinner(snapshot: LegacySnapshot, phase?: PhaseSummary): {
|
|
14
|
+
key: string | null;
|
|
15
|
+
label: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RESULTS_COPY, shippingTitle } from '../copy/results.js';
|
|
2
|
+
import { armLabel } from '../labels.js';
|
|
3
|
+
/** Stands in for CORE-15a winnerArm, borrowing the live decision only for the newest rollout. */ export function legacyRolloutLine(snapshot, phase, phases, decision) {
|
|
4
|
+
const newestRollout = phases.filter((phase)=>phase.phaseKind === 'rollout').reduce((latest, phase)=>latest === null || phase.phase > latest ? phase.phase : latest, null);
|
|
5
|
+
const winner = snapshot.winnerArm ?? (snapshot.phase === newestRollout ? decision?.winnerArm : undefined);
|
|
6
|
+
return shippingTitle(winner ? phase.arms.find((arm)=>arm.key === winner)?.label ?? armLabel(winner) : RESULTS_COPY.shippedGroup);
|
|
7
|
+
}
|
|
8
|
+
/** Stands in for CORE-15a minExposuresPerArm; absent historical minima keep the old dash. */ export function legacyMinimum(snapshot, ctx) {
|
|
9
|
+
return snapshot.minExposuresPerArm ?? ctx.minExposuresPerArm;
|
|
10
|
+
}
|
|
11
|
+
/** Stands in for CORE-15a minExposuresPerArm by deriving the old notice from the frozen verdict. */ export function legacyTooFew(snapshot) {
|
|
12
|
+
return snapshot.verdict.code === 'collecting' || snapshot.verdict.code === 'rollout-collecting';
|
|
13
|
+
}
|
|
14
|
+
/** Stands in for CORE-15a winnerArm in rollout recommendations using the old recommended arm and label. */ export function legacyRecommendationWinner(snapshot, phase) {
|
|
15
|
+
const key = snapshot.winnerArm ?? snapshot.verdict.recommendedArm;
|
|
16
|
+
const stored = phase?.arms.find((arm)=>arm.key === key)?.label.trim();
|
|
17
|
+
return {
|
|
18
|
+
key,
|
|
19
|
+
label: stored || (key && [
|
|
20
|
+
'control',
|
|
21
|
+
'variantA',
|
|
22
|
+
'variantB',
|
|
23
|
+
'variantC',
|
|
24
|
+
'holdout'
|
|
25
|
+
].includes(key) ? armLabel(key) : RESULTS_COPY.shippedGroup)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { DocId, Status } from '../../types/contracts.js';
|
|
2
2
|
import type { PhaseSummary, ResultsResponse } from '../../types/endpoints.js';
|
|
3
3
|
import type { Snapshot } from '../../types/snapshot.js';
|
|
4
|
+
import { type DisplaySnapshot } from './snapshot.js';
|
|
5
|
+
import type { PlacementLabel } from '../placements/placementLabels.model.js';
|
|
4
6
|
type Metric = Snapshot['metrics'][number];
|
|
5
7
|
type Arm = Metric['perArm'][number];
|
|
6
8
|
type Decision = {
|
|
7
9
|
phase: number;
|
|
8
10
|
winnerArm?: string;
|
|
9
11
|
};
|
|
10
|
-
type DisplaySnapshot = Snapshot;
|
|
11
12
|
export declare function defaultRound({ phases, decision, status }: Pick<ResultsResponse, 'phases'> & {
|
|
12
13
|
decision?: Decision | null;
|
|
13
14
|
status: Status;
|
|
@@ -26,8 +27,8 @@ export type VerdictCardModel = {
|
|
|
26
27
|
guardrailLine?: string;
|
|
27
28
|
};
|
|
28
29
|
export declare function verdictCard({ snapshot, decisionSnapshot, isDecidedTestRound, winnerLabel }: {
|
|
29
|
-
snapshot:
|
|
30
|
-
decisionSnapshot?:
|
|
30
|
+
snapshot: DisplaySnapshot;
|
|
31
|
+
decisionSnapshot?: DisplaySnapshot;
|
|
31
32
|
isDecidedTestRound: boolean;
|
|
32
33
|
winnerLabel: string;
|
|
33
34
|
}): VerdictCardModel;
|
|
@@ -73,11 +74,11 @@ export interface ArmRow {
|
|
|
73
74
|
comparison?: ComparisonModel;
|
|
74
75
|
}
|
|
75
76
|
export declare function armRows({ snapshot, phase, metricId }: {
|
|
76
|
-
snapshot:
|
|
77
|
+
snapshot: DisplaySnapshot;
|
|
77
78
|
phase: PhaseSummary;
|
|
78
79
|
metricId: DocId;
|
|
79
80
|
}): ArmRow[];
|
|
80
|
-
export declare function guardrailChips(snapshot:
|
|
81
|
+
export declare function guardrailChips(snapshot: DisplaySnapshot): {
|
|
81
82
|
name: string;
|
|
82
83
|
status: "OK" | "Watch" | "Failing" | "Too little data";
|
|
83
84
|
}[];
|
|
@@ -88,6 +89,7 @@ interface RoundLineInput {
|
|
|
88
89
|
}
|
|
89
90
|
export declare function powerLine({ snapshot, phaseKind, status }: RoundLineInput): string | null;
|
|
90
91
|
export interface ResultsContext {
|
|
92
|
+
placementLabels?: Map<string, PlacementLabel>;
|
|
91
93
|
status?: string;
|
|
92
94
|
phase?: PhaseSummary;
|
|
93
95
|
minExposuresPerArm?: number;
|
|
@@ -102,8 +104,9 @@ export interface DataQualityItem {
|
|
|
102
104
|
message: string;
|
|
103
105
|
detail?: string;
|
|
104
106
|
nextStep: string;
|
|
107
|
+
href?: string;
|
|
105
108
|
shares?: string[];
|
|
106
109
|
}
|
|
107
110
|
export declare function dataQuality(snapshot: DisplaySnapshot, ctx?: ResultsContext): DataQualityItem[];
|
|
108
|
-
export declare function verdictLabel(snapshot:
|
|
111
|
+
export declare function verdictLabel(snapshot: DisplaySnapshot): string;
|
|
109
112
|
export {};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { READINESS_COPY, versionPageUnavailableCopy, versionPageUncheckedCopy } from '../copy/readiness.js';
|
|
1
|
+
import { READINESS_COPY, placementUncheckedCopy, versionPageUnavailableCopy, versionPageUncheckedCopy } from '../copy/readiness.js';
|
|
2
2
|
import { armLabel, phaseKindLabel, verdictCodeLabel } from '../labels.js';
|
|
3
3
|
import { RESULTS_COPY, cappedCount, collectingCopy, conflictCopy, decidedTitle, exclusionCopy, ignoredCopy, metricUnavailable, moreDays, roundLabel, shareCopy, shippingTitle, srmP, uncappedTotal } from '../copy/results.js';
|
|
4
4
|
import { formatCI, formatCount, formatMetric, formatPercent, formatProbability } from './format.js';
|
|
5
|
+
import { legacyMinimum, legacyRolloutLine, legacyTooFew } from './legacy.js';
|
|
6
|
+
import { isLegacySnapshot } from './snapshot.js';
|
|
7
|
+
import { placementDetailKey } from '../placements/placementLabels.model.js';
|
|
5
8
|
export function defaultRound({ phases, decision, status }) {
|
|
6
9
|
if ((status === 'decided' || status === 'archived') && decision) return decision.phase;
|
|
7
10
|
return phases.reduce((latest, phase)=>latest === null || phase.phase > latest ? phase.phase : latest, null);
|
|
@@ -29,9 +32,10 @@ export function verdictCard({ snapshot, decisionSnapshot, isDecidedTestRound, wi
|
|
|
29
32
|
}
|
|
30
33
|
export function rolloutLine(snapshot, phase, phases, decision) {
|
|
31
34
|
if (snapshot.phaseKind !== 'rollout') return null;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
return isLegacySnapshot(snapshot) ? legacyRolloutLine(snapshot, phase, phases, decision) : currentRolloutLine(snapshot, phase);
|
|
36
|
+
}
|
|
37
|
+
function currentRolloutLine(snapshot, phase) {
|
|
38
|
+
return snapshot.winnerArm === null ? null : shippingTitle(phase.arms.find((arm)=>arm.key === snapshot.winnerArm)?.label ?? armLabel(snapshot.winnerArm));
|
|
35
39
|
}
|
|
36
40
|
export function cappedCell({ metric, arm, caps }) {
|
|
37
41
|
const cap = caps?.[metric.metricId];
|
|
@@ -125,14 +129,23 @@ function smallest(values) {
|
|
|
125
129
|
if (phaseKind !== 'rollout' || snapshot.verdict.code !== 'rollout-collecting') return null;
|
|
126
130
|
const keys = comparedKeys(snapshot, ctx.phase);
|
|
127
131
|
const primary = snapshot.metrics.find((metric)=>metric.role === 'primary');
|
|
128
|
-
return collectingCopy(formatCount(smallest(keys.map((key)=>snapshot.exposures.perArm[key]))), formatCount(snapshot
|
|
132
|
+
return collectingCopy(formatCount(smallest(keys.map((key)=>snapshot.exposures.perArm[key]))), formatCount(isLegacySnapshot(snapshot) ? legacyMinimum(snapshot, ctx) : snapshot.minExposuresPerArm), formatCount(smallest(keys.map((key)=>primary?.perArm.find((arm)=>arm.arm === key)?.converters))), formatCount(primary ? snapshot.params.minSampleSizes[primary.metricId] : undefined));
|
|
129
133
|
}
|
|
130
134
|
export function dataQuality(snapshot, ctx = {}) {
|
|
131
135
|
const items = (snapshot.health ?? []).map((item)=>({
|
|
132
|
-
id: `${item.code}:${item.path}`,
|
|
133
|
-
message: READINESS_COPY.needsAttention,
|
|
134
|
-
|
|
136
|
+
id: item.code === 'placement-unchecked' ? `${item.code}:${JSON.stringify(item.detail)}` : `${item.code}:${'path' in item ? item.path : item.metricId}`,
|
|
137
|
+
message: item.code === 'placement-unchecked' ? placementUncheckedCopy(ctx.placementLabels?.get(placementDetailKey(item.detail))?.title ?? item.detail.path ?? 'This page') : item.code === 'goal-in-some-versions' ? `${item.name} now appears in some versions but not all, so this result is no longer a fair comparison. Add it to every version, or remove it.` : READINESS_COPY.needsAttention,
|
|
138
|
+
...item.code === 'placement-unchecked' ? {
|
|
139
|
+
href: ctx.placementLabels?.get(placementDetailKey(item.detail))?.href
|
|
140
|
+
} : {},
|
|
141
|
+
nextStep: item.code === 'placement-unchecked' ? `Open ${ctx.placementLabels?.get(placementDetailKey(item.detail))?.title ?? item.detail.path ?? 'the page'}` : item.code === 'goal-in-some-versions' ? 'Edit the versions' : item.code === 'version-page-unavailable' ? versionPageUnavailableCopy(item.path, ctx.status) : versionPageUncheckedCopy(item.path, ctx.status)
|
|
135
142
|
}));
|
|
143
|
+
// Test snapshots store null by design; only rollout snapshots record the shipped winner.
|
|
144
|
+
if (!isLegacySnapshot(snapshot) && snapshot.phaseKind === 'rollout' && snapshot.winnerArm === null) items.push({
|
|
145
|
+
id: 'missing-winner',
|
|
146
|
+
message: RESULTS_COPY.missingWinner,
|
|
147
|
+
nextStep: RESULTS_COPY.missingWinnerNext
|
|
148
|
+
});
|
|
136
149
|
const srmTotal = Object.keys(snapshot.srm.expected).reduce((total, key)=>total + (snapshot.exposures.perArm[key] ?? 0), 0);
|
|
137
150
|
if (snapshot.srm.flagged) items.push({
|
|
138
151
|
id: 'srm',
|
|
@@ -159,7 +172,7 @@ export function dataQuality(snapshot, ctx = {}) {
|
|
|
159
172
|
detail: conflictCopy(formatPercent(exposures.identityConflictShare)),
|
|
160
173
|
nextStep: RESULTS_COPY.identityNext
|
|
161
174
|
});
|
|
162
|
-
if (snapshot.verdict.code === 'collecting' || snapshot.verdict.code === 'rollout-collecting') items.push({
|
|
175
|
+
if (isLegacySnapshot(snapshot) ? legacyTooFew(snapshot) : snapshot.verdict.code === 'collecting' || snapshot.verdict.code === 'rollout-collecting') items.push({
|
|
163
176
|
id: 'too-few',
|
|
164
177
|
message: RESULTS_COPY.tooFew,
|
|
165
178
|
nextStep: RESULTS_COPY.tooFewNext
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snapshot } from '../../types/snapshot.js';
|
|
2
|
+
type Core15aFields = Pick<Snapshot, 'winnerArm' | 'minExposuresPerArm'>;
|
|
3
|
+
/** Immutable history written before CORE-15a, with at least one added field absent. */
|
|
4
|
+
export type LegacySnapshot = Omit<Snapshot, keyof Core15aFields> & ({
|
|
5
|
+
winnerArm?: undefined;
|
|
6
|
+
minExposuresPerArm?: number;
|
|
7
|
+
} | {
|
|
8
|
+
winnerArm?: string | null;
|
|
9
|
+
minExposuresPerArm?: undefined;
|
|
10
|
+
});
|
|
11
|
+
export type DisplaySnapshot = Snapshot | LegacySnapshot;
|
|
12
|
+
/** A stored null winner is current data; only missing CORE-15a fields mark history. */
|
|
13
|
+
export declare function isLegacySnapshot(snapshot: DisplaySnapshot): snapshot is LegacySnapshot;
|
|
14
|
+
export {};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
-
import { Link } from '@payloadcms/ui';
|
|
5
4
|
import { formatAdminURL } from 'payload/shared';
|
|
5
|
+
import { fetchAllDocs } from '../../lib/fetchAllDocs.js';
|
|
6
6
|
import { createApi } from '../api.js';
|
|
7
7
|
import { noMatchesLine, SURFACE_COPY } from '../copy/surface.js';
|
|
8
8
|
import { Banner, EmptyState, StatusChip } from '../../ui/Feedback.js';
|
|
9
9
|
import { Button } from '../../ui/Button.js';
|
|
10
10
|
import { SearchInput } from '../../ui/Inputs.js';
|
|
11
11
|
import { SurfaceShell } from './SurfaceShell.js';
|
|
12
|
+
import { sanitizeReadinessDetail } from '../../sanitizeReadinessDetail.js';
|
|
12
13
|
import { groupTests, homeCard } from './home.model.js';
|
|
13
14
|
const HOME_FIELDS = [
|
|
14
15
|
'name',
|
|
@@ -19,7 +20,8 @@ const HOME_FIELDS = [
|
|
|
19
20
|
'decidedAt',
|
|
20
21
|
'decision',
|
|
21
22
|
'unitType',
|
|
22
|
-
'flagPoint'
|
|
23
|
+
'flagPoint',
|
|
24
|
+
'updatedAt'
|
|
23
25
|
];
|
|
24
26
|
// Typecheck fails if HomeTest gains a field the list request does not select (Payload always returns id).
|
|
25
27
|
const homeFieldsComplete = true;
|
|
@@ -42,20 +44,48 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
42
44
|
});
|
|
43
45
|
async function load() {
|
|
44
46
|
try {
|
|
45
|
-
const response = await
|
|
47
|
+
const response = await fetchAllDocs(`${apiRoute.replace(/\/$/, '')}/xp-experiments?depth=0&sort=id&${HOME_SELECT}`, {
|
|
46
48
|
credentials: 'include',
|
|
47
49
|
signal: controller.signal
|
|
48
50
|
});
|
|
49
51
|
if (!response.ok) throw new Error('list');
|
|
50
|
-
const
|
|
52
|
+
const docs = response.docs.sort((a, b)=>(Date.parse(b.updatedAt ?? '') || 0) - (Date.parse(a.updatedAt ?? '') || 0));
|
|
51
53
|
if (!active) return;
|
|
52
54
|
const summary = await api.summary(docs.map((doc)=>doc.id));
|
|
55
|
+
const summaries = summary.ok ? summary.data ?? {} : {};
|
|
56
|
+
const ids = [
|
|
57
|
+
...new Set(docs.flatMap((doc)=>{
|
|
58
|
+
const item = summaries[doc.id];
|
|
59
|
+
if (!canManage || ![
|
|
60
|
+
'draft',
|
|
61
|
+
'stopped'
|
|
62
|
+
].includes(doc.status) || item?.ready === true || item?.next?.code !== 'goal-in-some-versions') return [];
|
|
63
|
+
const detail = sanitizeReadinessDetail(item.next.code, item.next.detail);
|
|
64
|
+
return detail?.map((row)=>String(row.metricId)) ?? [];
|
|
65
|
+
}))
|
|
66
|
+
];
|
|
67
|
+
const goalNames = {};
|
|
68
|
+
if (ids.length) {
|
|
69
|
+
try {
|
|
70
|
+
const query = new URLSearchParams({
|
|
71
|
+
'where[id][in]': ids.join(','),
|
|
72
|
+
depth: '0',
|
|
73
|
+
limit: String(ids.length)
|
|
74
|
+
});
|
|
75
|
+
const response = await fetch(`${apiRoute.replace(/\/$/, '')}/xp-metrics?${query}`, {
|
|
76
|
+
credentials: 'include',
|
|
77
|
+
signal: controller.signal
|
|
78
|
+
});
|
|
79
|
+
const body = response.ok ? await response.json() : undefined;
|
|
80
|
+
for (const goal of body?.docs ?? [])if (typeof goal.name === 'string' && goal.name.trim() && ids.includes(String(goal.id))) goalNames[String(goal.id)] = goal.name;
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
53
83
|
if (active) setState({
|
|
54
84
|
loading: false,
|
|
55
85
|
error: false,
|
|
56
86
|
tests: docs,
|
|
57
|
-
|
|
58
|
-
summaries
|
|
87
|
+
goalNames,
|
|
88
|
+
summaries,
|
|
59
89
|
summaryError: !summary.ok
|
|
60
90
|
});
|
|
61
91
|
} catch {
|
|
@@ -73,7 +103,8 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
73
103
|
}, [
|
|
74
104
|
api,
|
|
75
105
|
apiRoute,
|
|
76
|
-
attempt
|
|
106
|
+
attempt,
|
|
107
|
+
canManage
|
|
77
108
|
]);
|
|
78
109
|
const retry = /*#__PURE__*/ _jsx(Button, {
|
|
79
110
|
size: "sm",
|
|
@@ -152,20 +183,17 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
|
|
|
152
183
|
}) : /*#__PURE__*/ _jsx(TestGroups, {
|
|
153
184
|
tests: state.tests,
|
|
154
185
|
summaries: state.summaries,
|
|
186
|
+
goalNames: state.goalNames,
|
|
155
187
|
adminRoute: adminRoute,
|
|
156
188
|
flagPoints: flagPoints,
|
|
157
189
|
search: search,
|
|
158
190
|
clear: ()=>setSearch('')
|
|
159
|
-
})
|
|
160
|
-
state.limited ? /*#__PURE__*/ _jsx("p", {
|
|
161
|
-
className: "xp-tests-limited",
|
|
162
|
-
children: SURFACE_COPY.limitedList
|
|
163
|
-
}) : null
|
|
191
|
+
})
|
|
164
192
|
]
|
|
165
193
|
})
|
|
166
194
|
});
|
|
167
195
|
}
|
|
168
|
-
function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear }) {
|
|
196
|
+
function TestGroups({ tests, summaries, goalNames, adminRoute, flagPoints, search, clear }) {
|
|
169
197
|
const groups = groupTests(tests, summaries, search);
|
|
170
198
|
if (!groups.some((group)=>group.tests.length)) return /*#__PURE__*/ _jsxs("div", {
|
|
171
199
|
className: "xp-tests-no-matches",
|
|
@@ -187,7 +215,7 @@ function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear })
|
|
|
187
215
|
const rows = /*#__PURE__*/ _jsx("div", {
|
|
188
216
|
className: "xp-tests-list",
|
|
189
217
|
children: group.tests.map((test)=>{
|
|
190
|
-
const card = homeCard(test, summaries[test.id], adminRoute, flagPoints);
|
|
218
|
+
const card = homeCard(test, summaries[test.id], adminRoute, flagPoints, goalNames);
|
|
191
219
|
return /*#__PURE__*/ _jsxs("article", {
|
|
192
220
|
className: "xp-tests-row",
|
|
193
221
|
children: [
|
|
@@ -197,7 +225,7 @@ function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear })
|
|
|
197
225
|
/*#__PURE__*/ _jsxs("div", {
|
|
198
226
|
className: "xp-tests-row-name",
|
|
199
227
|
children: [
|
|
200
|
-
/*#__PURE__*/ _jsx(
|
|
228
|
+
/*#__PURE__*/ _jsx("a", {
|
|
201
229
|
href: card.href,
|
|
202
230
|
children: test.name
|
|
203
231
|
}),
|
|
@@ -28,7 +28,11 @@ function SurfacePage(props) {
|
|
|
28
28
|
flagPoints: props.flagPoints,
|
|
29
29
|
attributeKeys: props.attributeKeys,
|
|
30
30
|
metricSources: props.metricSources,
|
|
31
|
-
|
|
31
|
+
admin: props.admin,
|
|
32
|
+
pageCollections: props.pageCollections,
|
|
33
|
+
collectionTitles: props.collectionTitles,
|
|
34
|
+
userId: props.userId,
|
|
35
|
+
userCollection: props.userCollection
|
|
32
36
|
};
|
|
33
37
|
const crumbs = [
|
|
34
38
|
{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentType } from 'react';
|
|
2
|
+
import type { TestsSurfaceProps } from '../../types/surface.js';
|
|
2
3
|
import type { SerializedAttributeKeys, SerializedFlagPoint, SerializedMetricSource } from '../../types/admin.js';
|
|
3
4
|
import type { useTestDocument } from './useTestDocument.js';
|
|
4
5
|
/** Sections of the test page, in readiness order (surface spec §6.2, §8). */
|
|
@@ -10,17 +11,17 @@ export type TestDocApi = ReturnType<typeof useTestDocument>;
|
|
|
10
11
|
export interface SurfaceProps {
|
|
11
12
|
adminRoute: string;
|
|
12
13
|
apiRoute: string;
|
|
14
|
+
collectionTitles?: Record<string, string>;
|
|
15
|
+
userId?: string | number;
|
|
16
|
+
userCollection?: string;
|
|
13
17
|
canManage: boolean;
|
|
14
18
|
canReadResults: boolean;
|
|
15
19
|
isAdmin: boolean;
|
|
16
20
|
flagPoints: SerializedFlagPoint[];
|
|
17
21
|
attributeKeys: SerializedAttributeKeys;
|
|
18
22
|
metricSources: SerializedMetricSource[];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
document?: string;
|
|
22
|
-
setting?: string;
|
|
23
|
-
};
|
|
23
|
+
pageCollections?: TestsSurfaceProps['pageCollections'];
|
|
24
|
+
admin?: TestsSurfaceProps['admin'];
|
|
24
25
|
}
|
|
25
26
|
export interface SectionProps {
|
|
26
27
|
test: TestDocApi;
|