editor: experiments QA panel (P0 of experiments plan)
New Experiments section in the rail: lists tracking experiment rules (status, traffic, arms, targeting), shows this session's assignment via evaluate-experiments, per-arm analytics with winner/significance, a QA arm override (sets the storefront variant cookie), and a two-step pause/resume kill-switch. Admin reads degrade when the tracking gate refuses; eligibility always works. Mock tracking endpoints + harness tests. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
1 parent
40b33f89a4
commit
922562e1c2
6 files changed
+325
-1
No files matched your search
@@ -15,6 +15,11 @@ export interface MockOptions {
|
||||
export function createMockCms(opts: MockOptions = {}) {
|
||||
let graph = seedGraph()
|
||||
const published: string[] = []
|
||||
// --- mock slask-tracking experiments (P0 QA panel) ---
|
||||
const expRules: { experiment_id: string; targeting?: { field: string; op: string; value?: string }[]; traffic_fraction?: number; variant_weights?: Record<string, number>; disabled?: boolean }[] = [
|
||||
{ experiment_id: 'exp_hero', targeting: [{ field: 'page_path', op: 'prefix', value: '/' }], traffic_fraction: 1, variant_weights: { a: 1, b: 1 } },
|
||||
{ experiment_id: 'exp_checkout', targeting: [{ field: 'country', op: 'eq', value: 'SE' }], traffic_fraction: 0.5, variant_weights: { control: 9, onepage: 1 }, disabled: true },
|
||||
]
|
||||
const requests: string[] = []
|
||||
let previewApi = true
|
||||
// --- mock go-auth (RFC 8628 device grant) ---
|
||||
@@ -102,6 +107,44 @@ export function createMockCms(opts: MockOptions = {}) {
|
||||
}
|
||||
return json(200, walk(r[1]))
|
||||
}
|
||||
if (path === '/api/tracking/config' && m === 'GET') return json(200, { experiment_rules: expRules })
|
||||
if (path === '/api/tracking/evaluate-experiments' && m === 'POST') {
|
||||
const body = (await req.json()) as { experiments?: { id: string; cookieName?: string }[] }
|
||||
const cookies = Object.fromEntries((req.headers.get('cookie') ?? '').split(';').map((p) => p.trim().split('=').map((s) => s.trim())).filter((kv) => kv.length === 2)) as Record<string, string>
|
||||
const assignments = (body.experiments ?? []).map((e) => {
|
||||
const rule = expRules.find((r) => r.experiment_id === e.id)
|
||||
const cookieName = e.cookieName || `exp_${e.id}`
|
||||
if (!rule || rule.disabled) return { experimentId: e.id, cookieName, variant: '', eligible: false }
|
||||
const arms = Object.keys(rule.variant_weights ?? { a: 1, b: 1 })
|
||||
const forced = cookies[cookieName]
|
||||
return { experimentId: e.id, cookieName, variant: forced && arms.includes(forced) ? forced : arms[0], eligible: true }
|
||||
})
|
||||
return json(200, { assignments })
|
||||
}
|
||||
if (path === '/api/tracking/experiments/analytics' && m === 'GET')
|
||||
return json(200, [
|
||||
{
|
||||
experiment_id: 'exp_hero',
|
||||
variants: [
|
||||
{ variant: 'a', sessions: 120, conversions: 12, conversion_rate: 0.1 },
|
||||
{ variant: 'b', sessions: 130, conversions: 22, conversion_rate: 0.169 },
|
||||
],
|
||||
winner: 'b',
|
||||
winner_probability: 0.87,
|
||||
significant: true,
|
||||
conclusion: 'b beats a',
|
||||
total_sessions: 250,
|
||||
},
|
||||
])
|
||||
const expToggle = /^\/api\/tracking\/config\/experiments\/([^/]+)\/disabled$/.exec(path)
|
||||
if (expToggle && m === 'PATCH') {
|
||||
const rule = expRules.find((x) => x.experiment_id === decodeURIComponent(expToggle[1]))
|
||||
if (!rule) return json(404, { error: 'experiment rule not found' })
|
||||
const body = (await req.json()) as { disabled?: boolean }
|
||||
if (typeof body.disabled !== 'boolean') return json(422, { error: 'missing disabled' })
|
||||
rule.disabled = body.disabled
|
||||
return json(200, { disabled: rule.disabled })
|
||||
}
|
||||
if (path === '/api/edit' && m === 'POST') {
|
||||
const body = (await req.json()) as { ops: any[] }
|
||||
const res = graph.edit(body.ops)
|
||||
|
||||
Reference in new issue
Block a user